From e2ccdfd8123d1de30f99d315d02c720106576087 Mon Sep 17 00:00:00 2001 From: Jacques Distler Date: Mon, 16 Mar 2009 09:55:30 -0500 Subject: [PATCH] Instiki 0.16.5 Update to Rails 2.3.2 (the stable Rails 2.3 release). Add audio/speex support Update CHANGELOG Bump version number --- CHANGELOG | 9 + app/controllers/application_controller.rb | 4 +- test/functional/wiki_controller_test.rb | 6 +- vendor/rails/actionmailer/CHANGELOG | 5 +- vendor/rails/actionmailer/Rakefile | 2 +- .../actionmailer/lib/action_mailer/base.rb | 2 +- .../vendor/text-format-0.6.3/text/format.rb | 2 +- .../actionmailer/lib/action_mailer/version.rb | 2 +- .../actionmailer/test/mail_layout_test.rb | 30 +- .../actionmailer/test/mail_service_test.rb | 2 +- .../actionmailer/test/test_helper_test.rb | 8 +- vendor/rails/actionpack/CHANGELOG | 7 +- vendor/rails/actionpack/Rakefile | 3 +- .../rails/actionpack/lib/action_controller.rb | 7 +- .../actionpack/lib/action_controller/base.rb | 8 +- .../lib/action_controller/caching/actions.rb | 27 +- .../action_controller/http_authentication.rb | 72 ++- .../lib/action_controller/integration.rb | 2 +- .../lib/action_controller/layout.rb | 10 +- .../action_controller/polymorphic_routes.rb | 7 +- .../lib/action_controller/request.rb | 1 + .../lib/action_controller/resources.rb | 15 +- .../lib/action_controller/routing.rb | 2 +- .../lib/action_controller/routing/builder.rb | 3 +- .../routing/recognition_optimisation.rb | 1 - .../vendor/html-scanner/html/selector.rb | 2 +- .../action_controller/vendor/rack-1.0/rack.rb | 4 +- .../rack-1.0/rack/auth/abstract/handler.rb | 4 +- .../vendor/rack-1.0/rack/auth/digest/md5.rb | 2 +- .../rack-1.0/rack/auth/digest/request.rb | 2 +- .../vendor/rack-1.0/rack/builder.rb | 6 +- .../vendor/rack-1.0/rack/chunked.rb | 49 ++ .../vendor/rack-1.0/rack/content_length.rb | 8 +- .../vendor/rack-1.0/rack/content_type.rb | 23 + .../vendor/rack-1.0/rack/deflater.rb | 4 +- .../vendor/rack-1.0/rack/directory.rb | 6 +- .../vendor/rack-1.0/rack/file.rb | 2 +- .../vendor/rack-1.0/rack/handler/cgi.rb | 4 + .../vendor/rack-1.0/rack/handler/fastcgi.rb | 3 + .../vendor/rack-1.0/rack/handler/lsws.rb | 7 +- .../vendor/rack-1.0/rack/handler/mongrel.rb | 4 +- .../vendor/rack-1.0/rack/handler/scgi.rb | 4 +- .../vendor/rack-1.0/rack/handler/thin.rb | 3 + .../vendor/rack-1.0/rack/handler/webrick.rb | 10 +- .../vendor/rack-1.0/rack/lint.rb | 58 +- .../vendor/rack-1.0/rack/response.rb | 2 + .../vendor/rack-1.0/rack/showstatus.rb | 2 +- .../vendor/rack-1.0/rack/urlmap.rb | 6 +- .../vendor/rack-1.0/rack/utils.rb | 15 +- .../actionpack/lib/action_pack/version.rb | 2 +- .../rails/actionpack/lib/action_view/base.rb | 18 +- .../helpers/active_record_helper.rb | 4 +- .../lib/action_view/helpers/date_helper.rb | 4 +- .../action_view/helpers/form_tag_helper.rb | 4 +- .../lib/action_view/helpers/number_helper.rb | 6 +- .../action_view/helpers/prototype_helper.rb | 2 +- .../lib/action_view/helpers/text_helper.rb | 5 +- .../rails/actionpack/lib/action_view/paths.rb | 2 +- .../actionpack/lib/action_view/renderable.rb | 24 +- .../actionpack/lib/action_view/template.rb | 2 +- .../activerecord/active_record_store_test.rb | 25 + .../test/controller/assert_select_test.rb | 61 +- .../test/controller/caching_test.rb | 14 + .../actionpack/test/controller/fake_models.rb | 8 + .../controller/html-scanner/document_test.rb | 2 +- .../http_digest_authentication_test.rb | 53 +- .../test/controller/integration_test.rb | 6 +- .../actionpack/test/controller/layout_test.rb | 12 +- .../test/controller/mime_responds_test.rb | 2 +- .../controller/polymorphic_routes_test.rb | 85 +++ .../test/controller/redirect_test.rb | 4 +- .../actionpack/test/controller/render_test.rb | 60 +- .../request/multipart_params_parsing_test.rb | 2 +- .../request_forgery_protection_test.rb | 18 +- .../test/controller/request_test.rb | 6 +- .../test/controller/resources_test.rb | 72 ++- .../test/controller/routing_test.rb | 96 +++- .../test/controller/selector_test.rb | 6 +- .../test/controller/send_file_test.rb | 2 +- .../controller/session/cookie_store_test.rb | 30 +- .../session/mem_cache_store_test.rb | 44 +- .../test/controller/url_rewriter_test.rb | 2 +- .../fixtures/layouts/default_html.html.erb | 1 + .../quiz/questions/_question.html.erb | 1 + .../template/active_record_helper_test.rb | 34 ++ .../test/template/form_tag_helper_test.rb | 11 +- .../test/template/javascript_helper_test.rb | 5 - .../test/template/number_helper_test.rb | 1 + .../actionpack/test/template/render_test.rb | 4 + .../test/template/text_helper_test.rb | 6 + .../test/template/url_helper_test.rb | 2 +- .../lib/active_model/validations/inclusion.rb | 2 +- .../test/state_machine/event_test.rb | 2 +- vendor/rails/activerecord/CHANGELOG | 7 +- vendor/rails/activerecord/Rakefile | 2 +- .../lib/active_record/associations.rb | 29 +- .../associations/association_collection.rb | 57 +- .../has_many_through_association.rb | 6 +- .../associations/has_one_association.rb | 13 +- .../lib/active_record/autosave_association.rb | 4 +- .../activerecord/lib/active_record/base.rb | 43 +- .../activerecord/lib/active_record/batches.rb | 47 +- .../lib/active_record/calculations.rb | 26 +- .../connection_adapters/sqlite3_adapter.rb | 2 +- .../connection_adapters/sqlite_adapter.rb | 64 ++- .../lib/active_record/locking/optimistic.rb | 33 ++ .../lib/active_record/named_scope.rb | 12 +- .../lib/active_record/reflection.rb | 2 +- .../serializers/xml_serializer.rb | 18 +- .../lib/active_record/test_case.rb | 13 + .../lib/active_record/validations.rb | 17 +- .../activerecord/lib/active_record/version.rb | 2 +- .../belongs_to_associations_test.rb | 37 +- .../eager_load_nested_include_test.rb | 29 + .../test/cases/associations/eager_test.rb | 8 +- ...s_and_belongs_to_many_associations_test.rb | 37 +- .../has_many_associations_test.rb | 93 ++- .../has_many_through_associations_test.rb | 29 +- .../associations/has_one_associations_test.rb | 44 +- .../has_one_through_associations_test.rb | 20 +- .../test/cases/attribute_methods_test.rb | 2 +- .../test/cases/autosave_association_test.rb | 35 ++ .../activerecord/test/cases/base_test.rb | 37 +- .../activerecord/test/cases/batches_test.rb | 18 +- .../test/cases/calculations_test.rb | 25 +- .../activerecord/test/cases/callbacks_test.rb | 4 +- .../activerecord/test/cases/dirty_test.rb | 2 +- .../activerecord/test/cases/finder_test.rb | 79 +-- .../activerecord/test/cases/fixtures_test.rb | 4 +- .../test/cases/inheritance_test.rb | 4 +- .../activerecord/test/cases/locking_test.rb | 28 +- .../test/cases/method_scoping_test.rb | 44 +- .../activerecord/test/cases/migration_test.rb | 66 ++- .../test/cases/named_scope_test.rb | 46 +- .../test/cases/reflection_test.rb | 7 +- .../test/cases/transactions_test.rb | 17 +- .../test/cases/validations_test.rb | 48 +- .../test/cases/xml_serialization_test.rb | 13 +- .../rails/activerecord/test/fixtures/toys.yml | 4 + .../rails/activerecord/test/models/company.rb | 2 + .../rails/activerecord/test/models/event.rb | 3 + .../rails/activerecord/test/models/owner.rb | 3 +- vendor/rails/activerecord/test/models/pet.rb | 3 +- .../rails/activerecord/test/models/pirate.rb | 49 +- .../rails/activerecord/test/models/reply.rb | 6 + .../rails/activerecord/test/models/topic.rb | 12 +- vendor/rails/activerecord/test/models/toy.rb | 4 + .../rails/activerecord/test/schema/schema.rb | 9 + vendor/rails/activeresource/CHANGELOG | 9 +- vendor/rails/activeresource/Rakefile | 2 +- .../lib/active_resource/version.rb | 2 +- .../activeresource/test/abstract_unit.rb | 1 + .../activeresource/test/authorization_test.rb | 8 +- vendor/rails/activeresource/test/base_test.rb | 22 +- vendor/rails/activesupport/CHANGELOG | 8 +- .../core_ext/date/calculations.rb | 2 +- .../core_ext/hash/conversions.rb | 5 +- .../core_ext/hash/indifferent_access.rb | 6 + .../core_ext/string/inflections.rb | 4 +- .../lib/active_support/inflector.rb | 12 +- .../lib/active_support/json/decoding.rb | 22 +- .../lib/active_support/memoizable.rb | 4 + .../lib/active_support/multibyte/chars.rb | 16 +- .../testing/setup_and_teardown.rb | 7 +- .../vendor/i18n-0.1.3/test/i18n_test.rb | 4 +- .../i18n-0.1.3/test/simple_backend_test.rb | 26 +- .../lib/active_support/version.rb | 2 +- .../lib/active_support/xml_mini.rb | 128 +---- .../lib/active_support/xml_mini/libxml.rb | 133 +++++ .../lib/active_support/xml_mini/nokogiri.rb | 77 +++ .../lib/active_support/xml_mini/rexml.rb | 108 ++++ .../activesupport/test/core_ext/class_test.rb | 6 +- .../test/core_ext/hash_ext_test.rb | 26 +- .../test/core_ext/load_error_test.rb | 4 +- .../core_ext/module/synchronization_test.rb | 4 +- .../test/core_ext/module_test.rb | 10 +- .../core_ext/object_and_class_ext_test.rb | 4 +- .../test/core_ext/string_ext_test.rb | 18 + .../test/core_ext/time_with_zone_test.rb | 2 +- .../activesupport/test/dependencies_test.rb | 22 +- .../activesupport/test/inflector_test.rb | 14 +- .../test/inflector_test_cases.rb | 16 + .../activesupport/test/json/decoding_test.rb | 8 +- .../activesupport/test/json/encoding_test.rb | 2 +- .../activesupport/test/memoizable_test.rb | 23 +- .../test/message_encryptor_test.rb | 2 +- .../test/message_verifier_test.rb | 2 +- .../test/multibyte_chars_test.rb | 48 +- .../test/string_inquirer_test.rb | 2 +- .../activesupport/test/time_zone_test.rb | 2 +- .../test/xml_mini/nokogiri_engine_test.rb | 157 +++++ .../test/xml_mini/rexml_engine_test.rb | 15 + vendor/rails/railties/CHANGELOG | 5 +- vendor/rails/railties/Rakefile | 10 +- .../railties/builtin/rails_info/rails/info.rb | 4 + vendor/rails/railties/environments/boot.rb | 1 + .../guides/files/stylesheets/main.css | 16 +- .../railties/guides/images/error_messages.png | Bin 8440 -> 14645 bytes vendor/rails/railties/guides/images/fxn.jpg | Bin 0 -> 17868 bytes .../images/i18n/demo_localized_pirate.png | Bin 36500 -> 15027 bytes .../guides/images/i18n/demo_translated_en.png | Bin 32877 -> 12057 bytes .../images/i18n/demo_translated_pirate.png | Bin 34506 -> 13392 bytes .../images/i18n/demo_translation_missing.png | Bin 34373 -> 13143 bytes .../guides/images/i18n/demo_untranslated.png | Bin 32793 -> 11925 bytes vendor/rails/railties/guides/rails_guides.rb | 1 + .../railties/guides/rails_guides/generator.rb | 36 +- .../railties/guides/rails_guides/indexer.rb | 2 +- .../guides/rails_guides/levenshtein.rb | 112 ++++ .../guides/source/2_3_release_notes.textile | 115 ++-- .../source/action_controller_overview.textile | 32 +- .../source/action_mailer_basics.textile | 60 +- .../source/active_record_basics.textile | 8 +- .../source/active_record_querying.textile | 170 ++++-- ...activerecord_validations_callbacks.textile | 543 +++++++++++------- .../guides/source/association_basics.textile | 80 +-- .../guides/source/caching_with_rails.textile | 150 ++--- .../guides/source/command_line.textile | 43 +- .../railties/guides/source/contribute.textile | 22 +- .../source/contributing_to_rails.textile | 239 ++++++++ .../guides/source/credits.erb.textile | 33 +- .../debugging_rails_applications.textile | 12 +- .../guides/source/form_helpers.textile | 56 +- .../guides/source/getting_started.textile | 13 +- .../rails/railties/guides/source/i18n.textile | 294 +++++----- .../railties/guides/source/index.erb.textile | 10 +- .../railties/guides/source/layout.html.erb | 21 +- .../source/layouts_and_rendering.textile | 62 +- .../railties/guides/source/migrations.textile | 46 +- .../guides/source/nested_model_forms.textile | 222 +++++++ .../guides/source/performance_testing.textile | 60 +- .../railties/guides/source/plugins.textile | 34 +- .../guides/source/rails_on_rack.textile | 67 ++- .../railties/guides/source/routing.textile | 34 +- .../railties/guides/source/security.textile | 100 ++-- .../railties/guides/source/testing.textile | 30 +- vendor/rails/railties/lib/commands/plugin.rb | 7 +- vendor/rails/railties/lib/initializer.rb | 4 +- .../railties/lib/rails/backtrace_cleaner.rb | 20 +- .../railties/lib/rails/gem_dependency.rb | 239 ++++---- vendor/rails/railties/lib/rails/rack/metal.rb | 8 +- .../rails/railties/lib/rails/rack/static.rb | 17 +- vendor/rails/railties/lib/rails/version.rb | 2 +- .../applications/app/template_runner.rb | 4 +- vendor/rails/railties/lib/tasks/gems.rake | 78 ++- .../railties/test/backtrace_cleaner_test.rb | 29 + vendor/rails/railties/test/boot_test.rb | 2 + .../metal/multiplemetals/app/metal/metal_a.rb | 2 +- .../metal/multiplemetals/app/metal/metal_b.rb | 2 +- .../pluralmetal/app/metal/legacy_routes.rb | 5 + .../metal/singlemetal/app/metal/foo_metal.rb | 2 +- .../subfolders/app/metal/Folder/metal_a.rb | 2 +- .../subfolders/app/metal/Folder/metal_b.rb | 2 +- .../fixtures/plugins/engines/engine/init.rb | 2 +- .../test/fixtures/public/foo/bar.html | 1 + .../test/fixtures/public/foo/index.html | 1 + .../railties/test/fixtures/public/index.html | 1 + .../railties/test/gem_dependency_test.rb | 17 +- .../generators/rails_template_runner_test.rb | 5 + .../rails/railties/test/initializer_test.rb | 2 +- vendor/rails/railties/test/metal_test.rb | 15 + .../railties/test/plugin_locator_test.rb | 2 +- vendor/rails/railties/test/plugin_test.rb | 12 +- .../rails/railties/test/rack_static_test.rb | 46 ++ .../gems/dummy-gem-g-1.0.0/.specification | 2 +- 264 files changed, 4850 insertions(+), 1906 deletions(-) create mode 100644 vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/chunked.rb create mode 100644 vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_type.rb create mode 100644 vendor/rails/actionpack/test/fixtures/layouts/default_html.html.erb create mode 100644 vendor/rails/actionpack/test/fixtures/quiz/questions/_question.html.erb create mode 100644 vendor/rails/activerecord/test/fixtures/toys.yml create mode 100644 vendor/rails/activerecord/test/models/event.rb create mode 100644 vendor/rails/activerecord/test/models/toy.rb create mode 100644 vendor/rails/activesupport/lib/active_support/xml_mini/libxml.rb create mode 100644 vendor/rails/activesupport/lib/active_support/xml_mini/nokogiri.rb create mode 100644 vendor/rails/activesupport/lib/active_support/xml_mini/rexml.rb create mode 100644 vendor/rails/activesupport/test/xml_mini/nokogiri_engine_test.rb create mode 100644 vendor/rails/activesupport/test/xml_mini/rexml_engine_test.rb create mode 100644 vendor/rails/railties/guides/images/fxn.jpg create mode 100644 vendor/rails/railties/guides/rails_guides/levenshtein.rb create mode 100644 vendor/rails/railties/guides/source/contributing_to_rails.textile create mode 100644 vendor/rails/railties/guides/source/nested_model_forms.textile create mode 100644 vendor/rails/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb create mode 100644 vendor/rails/railties/test/fixtures/public/foo/bar.html create mode 100644 vendor/rails/railties/test/fixtures/public/foo/index.html create mode 100644 vendor/rails/railties/test/fixtures/public/index.html create mode 100644 vendor/rails/railties/test/rack_static_test.rb diff --git a/CHANGELOG b/CHANGELOG index f1206bb1..73679747 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,12 @@ +* 0.16.5: Rails 2.3.2 + +* Runs on the Stable Release, Rails 2.3.2. +* Support for audio/speex audio files. +* Updated for itex2MML 1.3.7. (You should + upgrade that, as well.) +* Tests for BlahTeX/PNG (if installed). + +------------------------------------------------------------------------------ * 0.16.4 New Features: diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 847af96b..5e046409 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -57,6 +57,7 @@ class ApplicationController < ActionController::Base '.mov' => 'video/quicktime', '.mp3' => 'audio/mpeg', '.mp4' => 'video/mp4', + '.spx' => 'audio/speex', '.txt' => 'text/plain', '.tex' => 'text/plain', '.wav' => 'audio/x-wav', @@ -72,6 +73,7 @@ class ApplicationController < ActionController::Base 'audio/mpeg' => 'inline', 'audio/x-wav' => 'inline', 'audio/x-aiff' => 'inline', + 'audio/speex' => 'inline', 'audio/ogg' => 'inline', 'video/ogg' => 'inline', 'video/mp4' => 'inline', @@ -260,7 +262,7 @@ module Instiki module VERSION #:nodoc: MAJOR = 0 MINOR = 16 - TINY = 4 + TINY = 5 SUFFIX = '(MML+)' PRERELEASE = false if PRERELEASE diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index 5ccbf6c5..81a9d2a3 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -208,9 +208,9 @@ class WikiControllerTest < ActionController::TestCase # end else - puts 'Warning: tests involving pdflatex are very slow, therefore they are disabled by default.' - puts ' Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable' - puts ' $INSTIKI_TEST_PDFLATEX to enable them.' +# puts 'Warning: tests involving pdflatex are very slow, therefore they are disabled by default.' +# puts ' Set environment variable INSTIKI_TEST_PDFLATEX or global Ruby variable' +# puts ' $INSTIKI_TEST_PDFLATEX to enable them.' end # def test_export_tex diff --git a/vendor/rails/actionmailer/CHANGELOG b/vendor/rails/actionmailer/CHANGELOG index 2a5e3b81..773e603d 100644 --- a/vendor/rails/actionmailer/CHANGELOG +++ b/vendor/rails/actionmailer/CHANGELOG @@ -1,10 +1,7 @@ -*2.3.1 [RC2] (March 5, 2009)* +*2.3.2 [Final] (March 15, 2009)* * Fixed that ActionMailer should send correctly formatted Return-Path in MAIL FROM for SMTP #1842 [Matt Jones] - -*2.3.0 [RC1] (February 1st, 2009)* - * Fixed RFC-2045 quoted-printable bug #1421 [squadette] * Fixed that no body charset would be set when there are attachments present #740 [Paweł Kondzior] diff --git a/vendor/rails/actionmailer/Rakefile b/vendor/rails/actionmailer/Rakefile index 7c27ef45..f06f18ff 100644 --- a/vendor/rails/actionmailer/Rakefile +++ b/vendor/rails/actionmailer/Rakefile @@ -55,7 +55,7 @@ spec = Gem::Specification.new do |s| s.rubyforge_project = "actionmailer" s.homepage = "http://www.rubyonrails.org" - s.add_dependency('actionpack', '= 2.3.1' + PKG_BUILD) + s.add_dependency('actionpack', '= 2.3.2' + PKG_BUILD) s.has_rdoc = true s.requirements << 'none' diff --git a/vendor/rails/actionmailer/lib/action_mailer/base.rb b/vendor/rails/actionmailer/lib/action_mailer/base.rb index db4589ee..b77409b6 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/base.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/base.rb @@ -479,7 +479,7 @@ module ActionMailer #:nodoc: ) end unless @parts.empty? - @content_type = "multipart/alternative" + @content_type = "multipart/alternative" if @content_type !~ /^multipart/ @parts = sort_parts(@parts, @implicit_parts_order) end end diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/text-format-0.6.3/text/format.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/text-format-0.6.3/text/format.rb index de054db8..2d20c7a6 100755 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/text-format-0.6.3/text/format.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/text-format-0.6.3/text/format.rb @@ -1150,7 +1150,7 @@ if __FILE__ == $0 assert_equal(Text::Format::JUSTIFY, @format_o.format_style) assert_match(/^of freedom, and that government of the people, by the people, for the$/, @format_o.format(GETTYSBURG).split("\n")[-3]) - assert_raises(ArgumentError) { @format_o.format_style = 33 } + assert_raise(ArgumentError) { @format_o.format_style = 33 } end def test_tag_paragraph diff --git a/vendor/rails/actionmailer/lib/action_mailer/version.rb b/vendor/rails/actionmailer/lib/action_mailer/version.rb index ac843ae6..08ff0d2f 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/version.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/version.rb @@ -2,7 +2,7 @@ module ActionMailer module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/actionmailer/test/mail_layout_test.rb b/vendor/rails/actionmailer/test/mail_layout_test.rb index c185bd5a..50901f52 100644 --- a/vendor/rails/actionmailer/test/mail_layout_test.rb +++ b/vendor/rails/actionmailer/test/mail_layout_test.rb @@ -21,10 +21,12 @@ class AutoLayoutMailer < ActionMailer::Base body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" }) end - def multipart(recipient) + def multipart(recipient, type = nil) recipients recipient subject "You have a mail" from "tester@example.com" + + content_type(type) if type end end @@ -64,6 +66,7 @@ class LayoutMailerTest < Test::Unit::TestCase def test_should_pickup_multipart_layout mail = AutoLayoutMailer.create_multipart(@recipient) + assert_equal "multipart/alternative", mail.content_type assert_equal 2, mail.parts.size assert_equal 'text/plain', mail.parts.first.content_type @@ -73,6 +76,31 @@ class LayoutMailerTest < Test::Unit::TestCase assert_equal "Hello from layout text/html multipart", mail.parts.last.body end + def test_should_pickup_multipartmixed_layout + mail = AutoLayoutMailer.create_multipart(@recipient, "multipart/mixed") + assert_equal "multipart/mixed", mail.content_type + assert_equal 2, mail.parts.size + + assert_equal 'text/plain', mail.parts.first.content_type + assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body + + assert_equal 'text/html', mail.parts.last.content_type + assert_equal "Hello from layout text/html multipart", mail.parts.last.body + end + + def test_should_fix_multipart_layout + mail = AutoLayoutMailer.create_multipart(@recipient, "text/plain") + assert_equal "multipart/alternative", mail.content_type + assert_equal 2, mail.parts.size + + assert_equal 'text/plain', mail.parts.first.content_type + assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body + + assert_equal 'text/html', mail.parts.last.content_type + assert_equal "Hello from layout text/html multipart", mail.parts.last.body + end + + def test_should_pickup_layout_given_to_render mail = AutoLayoutMailer.create_spam(@recipient) assert_equal "Spammer layout Hello, Earth", mail.body.strip diff --git a/vendor/rails/actionmailer/test/mail_service_test.rb b/vendor/rails/actionmailer/test/mail_service_test.rb index 26ba652f..277a9139 100644 --- a/vendor/rails/actionmailer/test/mail_service_test.rb +++ b/vendor/rails/actionmailer/test/mail_service_test.rb @@ -1069,7 +1069,7 @@ class RespondToTest < Test::Unit::TestCase end def test_should_still_raise_exception_with_expected_message_when_calling_an_undefined_method - error = assert_raises NoMethodError do + error = assert_raise NoMethodError do RespondToMailer.not_a_method end diff --git a/vendor/rails/actionmailer/test/test_helper_test.rb b/vendor/rails/actionmailer/test/test_helper_test.rb index 9d22bb26..65b07a71 100644 --- a/vendor/rails/actionmailer/test/test_helper_test.rb +++ b/vendor/rails/actionmailer/test/test_helper_test.rb @@ -26,7 +26,7 @@ class TestHelperMailerTest < ActionMailer::TestCase end def test_determine_default_mailer_raises_correct_error - assert_raises(ActionMailer::NonInferrableMailerError) do + assert_raise(ActionMailer::NonInferrableMailerError) do self.class.determine_default_mailer("NotAMailerTest") end end @@ -84,7 +84,7 @@ class TestHelperMailerTest < ActionMailer::TestCase end def test_assert_emails_too_few_sent - error = assert_raises ActiveSupport::TestCase::Assertion do + error = assert_raise ActiveSupport::TestCase::Assertion do assert_emails 2 do TestHelperMailer.deliver_test end @@ -94,7 +94,7 @@ class TestHelperMailerTest < ActionMailer::TestCase end def test_assert_emails_too_many_sent - error = assert_raises ActiveSupport::TestCase::Assertion do + error = assert_raise ActiveSupport::TestCase::Assertion do assert_emails 1 do TestHelperMailer.deliver_test TestHelperMailer.deliver_test @@ -105,7 +105,7 @@ class TestHelperMailerTest < ActionMailer::TestCase end def test_assert_no_emails_failure - error = assert_raises ActiveSupport::TestCase::Assertion do + error = assert_raise ActiveSupport::TestCase::Assertion do assert_no_emails do TestHelperMailer.deliver_test end diff --git a/vendor/rails/actionpack/CHANGELOG b/vendor/rails/actionpack/CHANGELOG index c4e4211c..8c9486cc 100644 --- a/vendor/rails/actionpack/CHANGELOG +++ b/vendor/rails/actionpack/CHANGELOG @@ -1,4 +1,4 @@ -*2.3.1 [RC2] (March 5, 2009)* +*2.3.2 [Final] (March 15, 2009)* * Fixed that redirection would just log the options, not the final url (which lead to "Redirected to #") [DHH] @@ -6,15 +6,14 @@ * 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] - -*2.3.0 [RC1] (February 1st, 2009)* - * 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| %> diff --git a/vendor/rails/actionpack/Rakefile b/vendor/rails/actionpack/Rakefile index 0d673c61..6cacdf3c 100644 --- a/vendor/rails/actionpack/Rakefile +++ b/vendor/rails/actionpack/Rakefile @@ -80,8 +80,7 @@ spec = Gem::Specification.new do |s| s.has_rdoc = true s.requirements << 'none' - s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD) - s.add_dependency('rack', '>= 0.9.0') + s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'action_controller' diff --git a/vendor/rails/actionpack/lib/action_controller.rb b/vendor/rails/actionpack/lib/action_controller.rb index ca826e7b..d03f4cb2 100644 --- a/vendor/rails/actionpack/lib/action_controller.rb +++ b/vendor/rails/actionpack/lib/action_controller.rb @@ -31,7 +31,12 @@ rescue LoadError end end -require 'action_controller/vendor/rack-1.0/rack' +begin + gem 'rack', '~> 1.0.0' + require 'rack' +rescue Gem::LoadError + require 'action_controller/vendor/rack-1.0/rack' +end module ActionController # TODO: Review explicit to see if they will automatically be handled by diff --git a/vendor/rails/actionpack/lib/action_controller/base.rb b/vendor/rails/actionpack/lib/action_controller/base.rb index b769a2e6..0facf706 100644 --- a/vendor/rails/actionpack/lib/action_controller/base.rb +++ b/vendor/rails/actionpack/lib/action_controller/base.rb @@ -907,13 +907,15 @@ module ActionController #:nodoc: extra_options[:template] = options end + options = extra_options + elsif !options.is_a?(Hash) + extra_options[:partial] = options options = extra_options end layout = pick_layout(options) response.layout = layout.path_without_format_and_extension if layout logger.info("Rendering template within #{layout.path_without_format_and_extension}") if logger && layout - layout = layout.path_without_format_and_extension if layout if content_type = options[:content_type] response.content_type = content_type.to_s @@ -1206,10 +1208,12 @@ module ActionController #:nodoc: cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip } cache_control << "max-age=#{seconds}" + cache_control.delete("no-cache") if options[:public] cache_control.delete("private") - cache_control.delete("no-cache") cache_control << "public" + else + cache_control << "private" end # This allows for additional headers to be passed through like 'max-stale' => 5.hours diff --git a/vendor/rails/actionpack/lib/action_controller/caching/actions.rb b/vendor/rails/actionpack/lib/action_controller/caching/actions.rb index 34e1c352..87b5029e 100644 --- a/vendor/rails/actionpack/lib/action_controller/caching/actions.rb +++ b/vendor/rails/actionpack/lib/action_controller/caching/actions.rb @@ -129,24 +129,23 @@ module ActionController #:nodoc: attr_reader :path, :extension class << self - def path_for(controller, options, infer_extension=true) + def path_for(controller, options, infer_extension = true) new(controller, options, infer_extension).path end end # When true, infer_extension will look up the cache path extension from the request's path & format. - # This is desirable when reading and writing the cache, but not when expiring the cache - expire_action should expire the same files regardless of the request format. - def initialize(controller, options = {}, infer_extension=true) - if infer_extension and options.is_a? Hash - request_extension = extract_extension(controller.request) - options = options.reverse_merge(:format => request_extension) + # This is desirable when reading and writing the cache, but not when expiring the cache - + # expire_action should expire the same files regardless of the request format. + def initialize(controller, options = {}, infer_extension = true) + if infer_extension + extract_extension(controller.request) + options = options.reverse_merge(:format => @extension) if options.is_a?(Hash) end + path = controller.url_for(options).split('://').last normalize!(path) - if infer_extension - @extension = request_extension - add_extension!(path, @extension) - end + add_extension!(path, @extension) @path = URI.unescape(path) end @@ -162,13 +161,7 @@ module ActionController #:nodoc: def extract_extension(request) # Don't want just what comes after the last '.' to accommodate multi part extensions # such as tar.gz. - extension = request.path[/^[^.]+\.(.+)$/, 1] - - # If there's no extension in the path, check request.format - if extension.nil? - extension = request.cache_format - end - extension + @extension = request.path[/^[^.]+\.(.+)$/, 1] || request.cache_format end end end diff --git a/vendor/rails/actionpack/lib/action_controller/http_authentication.rb b/vendor/rails/actionpack/lib/action_controller/http_authentication.rb index 2ccbc224..b6b5267c 100644 --- a/vendor/rails/actionpack/lib/action_controller/http_authentication.rb +++ b/vendor/rails/actionpack/lib/action_controller/http_authentication.rb @@ -68,8 +68,11 @@ module ActionController # # Simple Digest example: # + # require 'digest/md5' # class PostsController < ApplicationController - # USERS = {"dhh" => "secret"} + # REALM = "SuperSecret" + # USERS = {"dhh" => "secret", #plain text password + # "dap" => Digest:MD5::hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password # # before_filter :authenticate, :except => [:index] # @@ -83,14 +86,18 @@ module ActionController # # private # def authenticate - # authenticate_or_request_with_http_digest(realm) do |username| + # authenticate_or_request_with_http_digest(REALM) do |username| # USERS[username] # end # end # end # - # NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password so the framework can appropriately - # hash it to check the user's credentials. Returning +nil+ will cause authentication to fail. + # NOTE: 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. # # On shared hosts, Apache sometimes doesn't pass authentication headers to # FCGI instances. If your environment matches this description and you cannot @@ -177,26 +184,37 @@ module ActionController end # Raises error unless the request credentials response value matches the expected value. + # First try the password as a ha1 digest password. If this fails, then try it as a plain + # text password. def validate_digest_response(request, realm, &password_procedure) credentials = decode_credentials_header(request) valid_nonce = validate_nonce(request, credentials[:nonce]) - if valid_nonce && realm == credentials[:realm] && opaque(request.session.session_id) == credentials[:opaque] + if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque] password = password_procedure.call(credentials[:username]) - expected = expected_response(request.env['REQUEST_METHOD'], credentials[:uri], credentials, password) - expected == credentials[:response] + + [true, false].any? do |password_is_ha1| + expected = expected_response(request.env['REQUEST_METHOD'], request.env['REQUEST_URI'], credentials, password, password_is_ha1) + expected == credentials[:response] + end end end # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+ - def expected_response(http_method, uri, credentials, password) - ha1 = ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':')) + # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead + # of a plain-text password. + def expected_response(http_method, uri, credentials, password, password_is_ha1=true) + ha1 = password_is_ha1 ? password : ha1(credentials, password) ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':')) ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':')) end - def encode_credentials(http_method, credentials, password) - credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password) + def ha1(credentials, password) + ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':')) + end + + def encode_credentials(http_method, credentials, password, password_is_ha1) + credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1) "Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ') end @@ -213,8 +231,7 @@ module ActionController end def authentication_header(controller, realm) - session_id = controller.request.session.session_id - controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(session_id)}", opaque="#{opaque(session_id)}") + controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}") end def authentication_request(controller, realm, message = nil) @@ -252,23 +269,36 @@ module ActionController # POST or PUT 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. - def nonce(session_id, time = Time.now) + # The nonce is opaque to the client. Composed of Time, and hash of Time with secret + # key from the Rails session secret generated upon creation of project. Ensures + # the time cannot be modifed by client. + def nonce(time = Time.now) t = time.to_i - hashed = [t, session_id] + hashed = [t, secret_key] digest = ::Digest::MD5.hexdigest(hashed.join(":")) Base64.encode64("#{t}:#{digest}").gsub("\n", '') end - def validate_nonce(request, value) + # Might want a shorter timeout depending on whether the request + # is a 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(request, value, seconds_to_timeout=5*60) t = Base64.decode64(value).split(":").first.to_i - nonce(request.session.session_id, t) == value && (t - Time.now.to_i).abs <= 10 * 60 + nonce(t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout end - # Opaque based on digest of session_id - def opaque(session_id) - Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub("\n", '') + # Opaque based on random generation - but changing each request? + def opaque() + ::Digest::MD5.hexdigest(secret_key) end + + # Set in /initializers/session_store.rb, and loaded even if sessions are not in use. + def secret_key + ActionController::Base.session_options[:secret] + end + end end end diff --git a/vendor/rails/actionpack/lib/action_controller/integration.rb b/vendor/rails/actionpack/lib/action_controller/integration.rb index 1c05ab0b..26b69557 100644 --- a/vendor/rails/actionpack/lib/action_controller/integration.rb +++ b/vendor/rails/actionpack/lib/action_controller/integration.rb @@ -5,7 +5,7 @@ require 'active_support/test_case' module ActionController module Integration #:nodoc: # An integration Session instance represents a set of requests and responses - # performed sequentially by some virtual user. Becase you can instantiate + # performed sequentially by some virtual user. Because you can instantiate # multiple sessions and run them side-by-side, you can also mimic (to some # limited extent) multiple simultaneous users interacting with your system. # diff --git a/vendor/rails/actionpack/lib/action_controller/layout.rb b/vendor/rails/actionpack/lib/action_controller/layout.rb index ccd96055..6ec0c1b3 100644 --- a/vendor/rails/actionpack/lib/action_controller/layout.rb +++ b/vendor/rails/actionpack/lib/action_controller/layout.rb @@ -198,7 +198,7 @@ module ActionController #:nodoc: # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method # object). If the layout was defined without a directory, layouts is assumed. So layout "weblog/standard" will return # weblog/standard, but layout "standard" will return layouts/standard. - def active_layout(passed_layout = nil) + def active_layout(passed_layout = nil, options = {}) layout = passed_layout || default_layout return layout if layout.respond_to?(:render) @@ -208,7 +208,7 @@ module ActionController #:nodoc: else layout end - find_layout(active_layout, default_template_format) if active_layout + find_layout(active_layout, default_template_format, options[:html_fallback]) if active_layout end private @@ -220,8 +220,8 @@ module ActionController #:nodoc: nil end - def find_layout(layout, format) #:nodoc: - view_paths.find_template(layout.to_s =~ /layouts\// ? layout : "layouts/#{layout}", format, false) + def find_layout(layout, format, html_fallback=false) #:nodoc: + view_paths.find_template(layout.to_s =~ /layouts\// ? layout : "layouts/#{layout}", format, html_fallback) rescue ActionView::MissingTemplate raise if Mime::Type.lookup_by_extension(format.to_s).html? end @@ -234,7 +234,7 @@ module ActionController #:nodoc: when NilClass, TrueClass active_layout if action_has_layout? && candidate_for_layout?(:template => default_template_name) else - active_layout(layout) + active_layout(layout, :html_fallback => true) end else active_layout if action_has_layout? && candidate_for_layout?(options) diff --git a/vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb b/vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb index 924d1aa6..d9b614c2 100644 --- a/vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb @@ -163,7 +163,8 @@ module ActionController if parent.is_a?(Symbol) || parent.is_a?(String) string << "#{parent}_" else - string << "#{RecordIdentifier.__send__("singular_class_name", parent)}_" + string << "#{RecordIdentifier.__send__("plural_class_name", parent)}".singularize + string << "_" end end end @@ -171,7 +172,9 @@ module ActionController if record.is_a?(Symbol) || record.is_a?(String) route << "#{record}_" else - route << "#{RecordIdentifier.__send__("#{inflection}_class_name", record)}_" + route << "#{RecordIdentifier.__send__("plural_class_name", record)}" + route = route.singularize if inflection == :singular + route << "_" end action_prefix(options) + namespace + route + routing_type(options).to_s diff --git a/vendor/rails/actionpack/lib/action_controller/request.rb b/vendor/rails/actionpack/lib/action_controller/request.rb index 2cabab9e..ef223f15 100755 --- a/vendor/rails/actionpack/lib/action_controller/request.rb +++ b/vendor/rails/actionpack/lib/action_controller/request.rb @@ -442,6 +442,7 @@ EOM end def reset_session + @env['rack.session.options'].delete(:id) @env['rack.session'] = {} end diff --git a/vendor/rails/actionpack/lib/action_controller/resources.rb b/vendor/rails/actionpack/lib/action_controller/resources.rb index 3af21967..86abb7b2 100644 --- a/vendor/rails/actionpack/lib/action_controller/resources.rb +++ b/vendor/rails/actionpack/lib/action_controller/resources.rb @@ -91,7 +91,7 @@ module ActionController end def shallow_path_prefix - @shallow_path_prefix ||= "#{path_prefix unless @options[:shallow]}" + @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix end def member_path @@ -103,7 +103,7 @@ module ActionController end def shallow_name_prefix - @shallow_name_prefix ||= "#{name_prefix unless @options[:shallow]}" + @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix end def nesting_name_prefix @@ -630,7 +630,7 @@ module ActionController action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) action_path ||= Base.resources_path_names[action] || action - map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m) + map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true }) end end end @@ -641,9 +641,9 @@ module ActionController map_resource_routes(map, resource, :destroy, resource.member_path, route_path) end - def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil) + def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} ) if resource.has_action?(action) - action_options = action_options_for(action, resource, method) + action_options = action_options_for(action, resource, method, resource_options) formatted_route_path = "#{route_path}.:format" if route_name && @set.named_routes[route_name.to_sym].nil? @@ -660,9 +660,10 @@ module ActionController end end - def action_options_for(action, resource, method = nil) + def action_options_for(action, resource, method = nil, resource_options = {}) default_options = { :action => action.to_s } require_id = !resource.kind_of?(SingletonResource) + force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource) case default_options[:action] when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements) @@ -670,7 +671,7 @@ module ActionController when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id)) when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id)) when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id)) - else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements) + else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id)) end end end diff --git a/vendor/rails/actionpack/lib/action_controller/routing.rb b/vendor/rails/actionpack/lib/action_controller/routing.rb index a2141a77..c0eb6134 100644 --- a/vendor/rails/actionpack/lib/action_controller/routing.rb +++ b/vendor/rails/actionpack/lib/action_controller/routing.rb @@ -267,7 +267,7 @@ module ActionController module Routing SEPARATORS = %w( / . ? ) - HTTP_METHODS = [:get, :head, :post, :put, :delete] + HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set diff --git a/vendor/rails/actionpack/lib/action_controller/routing/builder.rb b/vendor/rails/actionpack/lib/action_controller/routing/builder.rb index 44d75944..d9590c88 100644 --- a/vendor/rails/actionpack/lib/action_controller/routing/builder.rb +++ b/vendor/rails/actionpack/lib/action_controller/routing/builder.rb @@ -159,7 +159,8 @@ module ActionController path = "/#{path}" unless path[0] == ?/ path = "#{path}/" unless path[-1] == ?/ - path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix] + prefix = options[:path_prefix].to_s.gsub(/^\//,'') + path = "/#{prefix}#{path}" unless prefix.blank? segments = segments_for_route_path(path) defaults, requirements, conditions = divide_route_options(segments, options) diff --git a/vendor/rails/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/vendor/rails/actionpack/lib/action_controller/routing/recognition_optimisation.rb index ebc55351..9bfebff0 100644 --- a/vendor/rails/actionpack/lib/action_controller/routing/recognition_optimisation.rb +++ b/vendor/rails/actionpack/lib/action_controller/routing/recognition_optimisation.rb @@ -98,7 +98,6 @@ module ActionController if Array === item i += 1 start = (i == 1) - final = (i == list.size) tag, sub = item if tag == :dynamic body += padding + "#{start ? 'if' : 'elsif'} true\n" diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb b/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb index 376bb874..e2c49c28 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb @@ -556,7 +556,7 @@ module HTML end # Attribute value. - next if statement.sub!(/^\[\s*([[:alpha:]][\w\-]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match| + next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match| name, equality, value = $1, $2, $3 if value == "?" value = values.shift diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb index 6c03b555..6349b950 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb @@ -23,14 +23,16 @@ module Rack # Return the Rack release as a dotted string. def self.release - "0.4" + "1.0 bundled" end autoload :Builder, "rack/builder" autoload :Cascade, "rack/cascade" + autoload :Chunked, "rack/chunked" autoload :CommonLogger, "rack/commonlogger" autoload :ConditionalGet, "rack/conditionalget" autoload :ContentLength, "rack/content_length" + autoload :ContentType, "rack/content_type" autoload :File, "rack/file" autoload :Deflater, "rack/deflater" autoload :Directory, "rack/directory" diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb index 8489c9b9..214df629 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb @@ -8,8 +8,8 @@ module Rack attr_accessor :realm - def initialize(app, &authenticator) - @app, @authenticator = app, authenticator + def initialize(app, realm=nil, &authenticator) + @app, @realm, @authenticator = app, realm, authenticator end diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb index 6d2bd29c..e579dc96 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb @@ -21,7 +21,7 @@ module Rack attr_writer :passwords_hashed - def initialize(app) + def initialize(*args) super @passwords_hashed = nil end diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb index a40f57b7..a8aa3bf9 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb @@ -8,7 +8,7 @@ module Rack class Request < Auth::AbstractRequest def method - @env['REQUEST_METHOD'] + @env['rack.methodoverride.original_method'] || @env['REQUEST_METHOD'] end def digest? diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/builder.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/builder.rb index 25994d5a..295235e5 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/builder.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/builder.rb @@ -34,11 +34,7 @@ module Rack end def use(middleware, *args, &block) - @ins << if block_given? - lambda { |app| middleware.new(app, *args, &block) } - else - lambda { |app| middleware.new(app, *args) } - end + @ins << lambda { |app| middleware.new(app, *args, &block) } end def run(app) diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/chunked.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/chunked.rb new file mode 100644 index 00000000..280d89dd --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/chunked.rb @@ -0,0 +1,49 @@ +require 'rack/utils' + +module Rack + + # Middleware that applies chunked transfer encoding to response bodies + # when the response does not include a Content-Length header. + class Chunked + include Rack::Utils + + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + headers = HeaderHash.new(headers) + + if env['HTTP_VERSION'] == 'HTTP/1.0' || + STATUS_WITH_NO_ENTITY_BODY.include?(status) || + headers['Content-Length'] || + headers['Transfer-Encoding'] + [status, headers.to_hash, body] + else + dup.chunk(status, headers, body) + end + end + + def chunk(status, headers, body) + @body = body + headers.delete('Content-Length') + headers['Transfer-Encoding'] = 'chunked' + [status, headers.to_hash, self] + end + + def each + term = "\r\n" + @body.each do |chunk| + size = bytesize(chunk) + next if size == 0 + yield [size.to_s(16), term, chunk, term].join + end + yield ["0", term, "", term].join + end + + def close + @body.close if @body.respond_to?(:close) + end + end +end diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_length.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_length.rb index bce22a32..1e56d438 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_length.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_length.rb @@ -3,21 +3,23 @@ require 'rack/utils' module Rack # Sets the Content-Length header on responses with fixed-length bodies. class ContentLength + include Rack::Utils + def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) - headers = Utils::HeaderHash.new(headers) + headers = HeaderHash.new(headers) - if !Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) && + if !STATUS_WITH_NO_ENTITY_BODY.include?(status) && !headers['Content-Length'] && !headers['Transfer-Encoding'] && (body.respond_to?(:to_ary) || body.respond_to?(:to_str)) body = [body] if body.respond_to?(:to_str) # rack 0.4 compat - length = body.to_ary.inject(0) { |len, part| len + part.length } + length = body.to_ary.inject(0) { |len, part| len + bytesize(part) } headers['Content-Length'] = length.to_s end diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_type.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_type.rb new file mode 100644 index 00000000..0c1e1ca3 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_type.rb @@ -0,0 +1,23 @@ +require 'rack/utils' + +module Rack + + # Sets the Content-Type header on responses which don't have one. + # + # Builder Usage: + # use Rack::ContentType, "text/plain" + # + # When no content type argument is provided, "text/html" is assumed. + class ContentType + def initialize(app, content_type = "text/html") + @app, @content_type = app, content_type + end + + def call(env) + status, headers, body = @app.call(env) + headers = Utils::HeaderHash.new(headers) + headers['Content-Type'] ||= @content_type + [status, headers.to_hash, body] + end + end +end diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/deflater.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/deflater.rb index 3e666800..a42b7477 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/deflater.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/deflater.rb @@ -36,12 +36,12 @@ module Rack mtime = headers.key?("Last-Modified") ? Time.httpdate(headers["Last-Modified"]) : Time.now body = self.class.gzip(body, mtime) - size = body.respond_to?(:bytesize) ? body.bytesize : body.size + size = Rack::Utils.bytesize(body) headers = headers.merge("Content-Encoding" => "gzip", "Content-Length" => size.to_s) [status, headers, [body]] when "deflate" body = self.class.deflate(body) - size = body.respond_to?(:bytesize) ? body.bytesize : body.size + size = Rack::Utils.bytesize(body) headers = headers.merge("Content-Encoding" => "deflate", "Content-Length" => size.to_s) [status, headers, [body]] when "identity" diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/directory.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/directory.rb index 56ee5e7b..acdd3029 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/directory.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/directory.rb @@ -70,7 +70,7 @@ table { width:100%%; } return unless @path_info.include? ".." body = "Forbidden\n" - size = body.respond_to?(:bytesize) ? body.bytesize : body.size + size = Rack::Utils.bytesize(body) return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]] end @@ -89,6 +89,8 @@ table { width:100%%; } type = stat.directory? ? 'directory' : Mime.mime_type(ext) size = stat.directory? ? '-' : filesize_format(size) mtime = stat.mtime.httpdate + url << '/' if stat.directory? + basename << '/' if stat.directory? @files << [ url, basename, size, type, mtime ] end @@ -120,7 +122,7 @@ table { width:100%%; } def entity_not_found body = "Entity not found: #{@path_info}\n" - size = body.respond_to?(:bytesize) ? body.bytesize : body.size + size = Rack::Utils.bytesize(body) return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]] end diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb index 7869227a..fe62bd6b 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb @@ -60,7 +60,7 @@ module Rack body = self else body = [F.read(@path)] - size = body.first.size + size = Utils.bytesize(body.first) end [200, { diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb index f2c976cf..e38156c7 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb @@ -1,3 +1,5 @@ +require 'rack/content_length' + module Rack module Handler class CGI @@ -6,6 +8,8 @@ module Rack end def self.serve(app) + app = ContentLength.new(app) + env = ENV.to_hash env.delete "HTTP_CONTENT_LENGTH" diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb index f03e1615..6324c7d2 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb @@ -1,5 +1,6 @@ require 'fcgi' require 'socket' +require 'rack/content_length' module Rack module Handler @@ -29,6 +30,8 @@ module Rack end def self.serve(request, app) + app = Rack::ContentLength.new(app) + env = request.env env.delete "HTTP_CONTENT_LENGTH" diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb index 1f850fc7..c65ba3ec 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb @@ -1,5 +1,6 @@ require 'lsapi' -#require 'cgi' +require 'rack/content_length' + module Rack module Handler class LSWS @@ -9,11 +10,13 @@ module Rack end end def self.serve(app) + app = Rack::ContentLength.new(app) + env = ENV.to_hash env.delete "HTTP_CONTENT_LENGTH" env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" env.update({"rack.version" => [0,1], - "rack.input" => $stdin, + "rack.input" => StringIO.new($stdin.read.to_s), "rack.errors" => $stderr, "rack.multithread" => false, "rack.multiprocess" => true, diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb index 178a1a8f..f0c0d583 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb @@ -1,5 +1,7 @@ require 'mongrel' require 'stringio' +require 'rack/content_length' +require 'rack/chunked' module Rack module Handler @@ -33,7 +35,7 @@ module Rack end def initialize(app) - @app = app + @app = Rack::Chunked.new(Rack::ContentLength.new(app)) end def process(request, response) diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb index fd18a835..9495c663 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb @@ -1,5 +1,7 @@ require 'scgi' require 'stringio' +require 'rack/content_length' +require 'rack/chunked' module Rack module Handler @@ -14,7 +16,7 @@ module Rack end def initialize(settings = {}) - @app = settings[:app] + @app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app])) @log = Object.new def @log.info(*args); end def @log.error(*args); end diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb index 7ad088b3..3d4fedff 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb @@ -1,9 +1,12 @@ require "thin" +require "rack/content_length" +require "rack/chunked" module Rack module Handler class Thin def self.run(app, options={}) + app = Rack::Chunked.new(Rack::ContentLength.new(app)) server = ::Thin::Server.new(options[:Host] || '0.0.0.0', options[:Port] || 8080, app) diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb index 40be79de..829e7d6b 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb @@ -1,5 +1,6 @@ require 'webrick' require 'stringio' +require 'rack/content_length' module Rack module Handler @@ -14,7 +15,7 @@ module Rack def initialize(server, app) super server - @app = app + @app = Rack::ContentLength.new(app) end def service(req, res) @@ -35,7 +36,12 @@ module Rack env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] env["QUERY_STRING"] ||= "" env["REQUEST_PATH"] ||= "/" - env.delete "PATH_INFO" if env["PATH_INFO"] == "" + if env["PATH_INFO"] == "" + env.delete "PATH_INFO" + else + path, n = req.request_uri.path, env["SCRIPT_NAME"].length + env["PATH_INFO"] = path[n, path.length-n] + end status, headers, body = @app.call(env) begin diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb index 7eb05437..44a33ce3 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb @@ -88,7 +88,9 @@ module Rack ## within the application. This may be an ## empty string, if the request URL targets ## the application root and does not have a - ## trailing slash. + ## trailing slash. This information should be + ## decoded by the server if it comes from a + ## URL. ## QUERY_STRING:: The portion of the request URL that ## follows the ?, if any. May be @@ -372,59 +374,43 @@ module Rack ## === The Content-Length def check_content_length(status, headers, env) - chunked_response = false - headers.each { |key, value| - if key.downcase == 'transfer-encoding' - chunked_response = value.downcase != 'identity' - end - } - headers.each { |key, value| if key.downcase == 'content-length' - ## There must be a Content-Length, except when the - ## +Status+ is 1xx, 204 or 304, in which case there must be none - ## given. + ## There must not be a Content-Length header when the + ## +Status+ is 1xx, 204 or 304. assert("Content-Length header found in #{status} response, not allowed") { not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i } - assert('Content-Length header should not be used if body is chunked') { - not chunked_response - } - bytes = 0 string_body = true - @body.each { |part| - unless part.kind_of?(String) - string_body = false - break - end + if @body.respond_to?(:to_ary) + @body.each { |part| + unless part.kind_of?(String) + string_body = false + break + end - bytes += (part.respond_to?(:bytesize) ? part.bytesize : part.size) - } - - if env["REQUEST_METHOD"] == "HEAD" - assert("Response body was given for HEAD request, but should be empty") { - bytes == 0 + bytes += Rack::Utils.bytesize(part) } - else - if string_body - assert("Content-Length header was #{value}, but should be #{bytes}") { - value == bytes.to_s + + if env["REQUEST_METHOD"] == "HEAD" + assert("Response body was given for HEAD request, but should be empty") { + bytes == 0 } + else + if string_body + assert("Content-Length header was #{value}, but should be #{bytes}") { + value == bytes.to_s + } + end end end return end } - - if [ String, Array ].include?(@body.class) && !chunked_response - assert('No Content-Length header found') { - Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i - } - end end ## === The Body diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/response.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/response.rb index a5931101..caf60d5b 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/response.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/response.rb @@ -16,6 +16,8 @@ module Rack # Your application's +call+ should end returning Response#finish. class Response + attr_accessor :length + def initialize(body=[], status=200, header={}, &block) @status = status @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}. diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb index 5f13404d..28258c7c 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb @@ -27,7 +27,7 @@ module Rack message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s detail = env["rack.showstatus.detail"] || message body = @template.result(binding) - size = body.respond_to?(:bytesize) ? body.bytesize : body.size + size = Rack::Utils.bytesize(body) [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]] else [status, headers, body] diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb index eb1457a8..0ff32df1 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb @@ -12,7 +12,11 @@ module Rack # first, since they are most specific. class URLMap - def initialize(map) + def initialize(map = {}) + remap(map) + end + + def remap(map) @mapping = map.map { |location, app| if location =~ %r{\Ahttps?://(.*?)(/.*)} host, location = $1, $2 diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb index f352cb67..0a61bce7 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb @@ -144,6 +144,19 @@ module Rack end module_function :select_best_encoding + # Return the bytesize of String; uses String#length under Ruby 1.8 and + # String#bytesize under 1.9. + if ''.respond_to?(:bytesize) + def bytesize(string) + string.bytesize + end + else + def bytesize(string) + string.size + end + end + module_function :bytesize + # Context allows the use of a compatible middleware at different points # in a request handling stack. A compatible middleware must define # #context which should take the arguments env and app. The first of which @@ -359,7 +372,7 @@ module Rack data = body end - Utils.normalize_params(params, name, data) + Utils.normalize_params(params, name, data) unless data.nil? break if buf.empty? || content_length == -1 } diff --git a/vendor/rails/actionpack/lib/action_pack/version.rb b/vendor/rails/actionpack/lib/action_pack/version.rb index f03a2a76..e0aa2a5f 100644 --- a/vendor/rails/actionpack/lib/action_pack/version.rb +++ b/vendor/rails/actionpack/lib/action_pack/version.rb @@ -2,7 +2,7 @@ module ActionPack #:nodoc: module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/actionpack/lib/action_view/base.rb b/vendor/rails/actionpack/lib/action_view/base.rb index fe6053e5..e19acc5c 100644 --- a/vendor/rails/actionpack/lib/action_view/base.rb +++ b/vendor/rails/actionpack/lib/action_view/base.rb @@ -221,10 +221,12 @@ module ActionView #:nodoc: def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc: @assigns = assigns_for_first_render @assigns_added = nil - @_render_stack = [] @controller = controller @helpers = ProxyModule.new(self) self.view_paths = view_paths + + @_first_render = nil + @_current_render = nil end attr_reader :view_paths @@ -286,7 +288,19 @@ module ActionView #:nodoc: # Access the current template being rendered. # Returns a ActionView::Template object. def template - @_render_stack.last + @_current_render + end + + def template=(template) #:nodoc: + @_first_render ||= template + @_current_render = template + end + + def with_template(current_template) + last_template, self.template = template, current_template + yield + ensure + self.template = last_template end private diff --git a/vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb index 8b56d241..541899ea 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb @@ -121,7 +121,7 @@ module ActionView if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) && (errors = obj.errors.on(method)) content_tag("div", - "#{options[:prepend_text]}#{errors.is_a?(Array) ? errors.first : errors}#{options[:append_text]}", + "#{options[:prepend_text]}#{ERB::Util.html_escape(errors.is_a?(Array) ? errors.first : errors)}#{options[:append_text]}", :class => options[:css_class] ) else @@ -198,7 +198,7 @@ module ActionView locale.t :header, :count => count, :model => object_name end message = options.include?(:message) ? options[:message] : locale.t(:body) - error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join + error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, ERB::Util.html_escape(msg)) } }.join contents = '' contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank? diff --git a/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb index b7ef1fb9..c74909a3 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb @@ -876,8 +876,8 @@ module ActionView input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') end - # Given an ordering of datetime components, create the selection html - # and join them with their appropriate seperators + # Given an ordering of datetime components, create the selection HTML + # and join them with their appropriate separators. def build_selects_from_types(order) select = '' order.reverse.each do |type| diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb index 4646bc11..6d39a53a 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -360,8 +360,8 @@ module ActionView end if confirm = options.delete("confirm") - options["onclick"] ||= '' - options["onclick"] << "return #{confirm_javascript_function(confirm)};" + options["onclick"] ||= 'return true;' + options["onclick"] = "if (!#{confirm_javascript_function(confirm)}) return false; #{options['onclick']}" end tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys) diff --git a/vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb index e622f97b..dea958de 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb @@ -15,6 +15,7 @@ module ActionView # * :country_code - Sets the country code for the phone number. # # ==== Examples + # 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 @@ -37,7 +38,8 @@ module ActionView str << if area_code number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4}$)/,"(\\1) \\2#{delimiter}\\3") else - number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") + number.gsub!(/([0-9]{0,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") + number.starts_with?('-') ? number.slice!(1..-1) : number end str << " x #{extension}" unless extension.blank? str @@ -138,7 +140,7 @@ module ActionView # number_with_delimiter(12345678) # => 12,345,678 # number_with_delimiter(12345678.05) # => 12,345,678.05 # number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678 - # number_with_delimiter(12345678, :seperator => ",") # => 12,345,678 + # number_with_delimiter(12345678, :separator => ",") # => 12,345,678 # number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",") # # => 98 765 432,98 # diff --git a/vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb index 18a209dc..91ef72e5 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -107,7 +107,7 @@ module ActionView # on the page in an Ajax response. module PrototypeHelper unless const_defined? :CALLBACKS - CALLBACKS = Set.new([ :uninitialized, :loading, :loaded, + CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded, :interactive, :complete, :failure, :success ] + (100..599).to_a) AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url, diff --git a/vendor/rails/actionpack/lib/action_view/helpers/text_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/text_helper.rb index 63fe0c1c..48bf4717 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/text_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/text_helper.rb @@ -536,8 +536,9 @@ module ActionView text.gsub(AUTO_LINK_RE) do href = $& punctuation = '' - # detect already linked URLs - if $` =~ /]*href="$/ + left, right = $`, $' + # detect already linked URLs and URLs in the middle of a tag + if left =~ /<[^>]+$/ && right =~ /^[^>]*>/ # do not change string; URL is alreay linked href else diff --git a/vendor/rails/actionpack/lib/action_view/paths.rb b/vendor/rails/actionpack/lib/action_view/paths.rb index 37d96b2f..8cc3fe29 100644 --- a/vendor/rails/actionpack/lib/action_view/paths.rb +++ b/vendor/rails/actionpack/lib/action_view/paths.rb @@ -61,7 +61,7 @@ module ActionView #:nodoc: end end - return Template.new(original_template_path, original_template_path =~ /\A\// ? "" : ".") if File.file?(original_template_path) + return Template.new(original_template_path, original_template_path.to_s =~ /\A\// ? "" : ".") if File.file?(original_template_path) raise MissingTemplate.new(self, original_template_path, format) end diff --git a/vendor/rails/actionpack/lib/action_view/renderable.rb b/vendor/rails/actionpack/lib/action_view/renderable.rb index 41080ed6..ff7bc7d9 100644 --- a/vendor/rails/actionpack/lib/action_view/renderable.rb +++ b/vendor/rails/actionpack/lib/action_view/renderable.rb @@ -27,23 +27,19 @@ module ActionView def render(view, local_assigns = {}) compile(local_assigns) - stack = view.instance_variable_get(:@_render_stack) - stack.push(self) + view.with_template self do + view.send(:_evaluate_assigns_and_ivars) + view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type) - view.send(:_evaluate_assigns_and_ivars) - view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type) - - result = view.send(method_name(local_assigns), local_assigns) do |*names| - ivar = :@_proc_for_layout - if !view.instance_variable_defined?(:"@content_for_#{names.first}") && view.instance_variable_defined?(ivar) && (proc = view.instance_variable_get(ivar)) - view.capture(*names, &proc) - elsif view.instance_variable_defined?(ivar = :"@content_for_#{names.first || :layout}") - view.instance_variable_get(ivar) + view.send(method_name(local_assigns), local_assigns) do |*names| + ivar = :@_proc_for_layout + if !view.instance_variable_defined?(:"@content_for_#{names.first}") && view.instance_variable_defined?(ivar) && (proc = view.instance_variable_get(ivar)) + view.capture(*names, &proc) + elsif view.instance_variable_defined?(ivar = :"@content_for_#{names.first || :layout}") + view.instance_variable_get(ivar) + end end end - - stack.pop - result end def method_name(local_assigns) diff --git a/vendor/rails/actionpack/lib/action_view/template.rb b/vendor/rails/actionpack/lib/action_view/template.rb index 0dd3a7e6..c339c8a5 100644 --- a/vendor/rails/actionpack/lib/action_view/template.rb +++ b/vendor/rails/actionpack/lib/action_view/template.rb @@ -218,7 +218,7 @@ module ActionView #:nodoc: # Returns file split into an array # [base_path, name, locale, format, extension] def split(file) - if m = file.match(/^(.*\/)?([^\.]+)\.(.*)$/) + if m = file.to_s.match(/^(.*\/)?([^\.]+)\.(.*)$/) base_path = m[1] name = m[2] extensions = m[3] diff --git a/vendor/rails/actionpack/test/activerecord/active_record_store_test.rb b/vendor/rails/actionpack/test/activerecord/active_record_store_test.rb index 7998f9c2..c98892ed 100644 --- a/vendor/rails/actionpack/test/activerecord/active_record_store_test.rb +++ b/vendor/rails/actionpack/test/activerecord/active_record_store_test.rb @@ -21,8 +21,15 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest render :text => "foo: #{session[:foo].inspect}" end + def get_session_id + session[:foo] + render :text => "#{request.session_options[:id]}" + end + def call_reset_session + session[:bar] reset_session + session[:bar] = "baz" head :ok end @@ -71,6 +78,7 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest get '/set_session_value' assert_response :success assert cookies['_session_id'] + session_id = cookies['_session_id'] get '/call_reset_session' assert_response :success @@ -79,6 +87,23 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest 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 end end diff --git a/vendor/rails/actionpack/test/controller/assert_select_test.rb b/vendor/rails/actionpack/test/controller/assert_select_test.rb index 99c57c0c..298c7e4d 100644 --- a/vendor/rails/actionpack/test/controller/assert_select_test.rb +++ b/vendor/rails/actionpack/test/controller/assert_select_test.rb @@ -76,7 +76,7 @@ class AssertSelectTest < ActionController::TestCase end def assert_failure(message, &block) - e = assert_raises(Assertion, &block) + e = assert_raise(Assertion, &block) assert_match(message, e.message) if Regexp === message assert_equal(message, e.message) if String === message end @@ -95,24 +95,24 @@ class AssertSelectTest < ActionController::TestCase def test_equality_true_false render_html %Q{
} assert_nothing_raised { assert_select "div" } - assert_raises(Assertion) { assert_select "p" } + assert_raise(Assertion) { assert_select "p" } assert_nothing_raised { assert_select "div", true } - assert_raises(Assertion) { assert_select "p", true } - assert_raises(Assertion) { assert_select "div", false } + assert_raise(Assertion) { assert_select "p", true } + assert_raise(Assertion) { assert_select "div", false } assert_nothing_raised { assert_select "p", false } end def test_equality_string_and_regexp render_html %Q{
foo
foo
} assert_nothing_raised { assert_select "div", "foo" } - assert_raises(Assertion) { assert_select "div", "bar" } + assert_raise(Assertion) { assert_select "div", "bar" } assert_nothing_raised { assert_select "div", :text=>"foo" } - assert_raises(Assertion) { assert_select "div", :text=>"bar" } + assert_raise(Assertion) { assert_select "div", :text=>"bar" } assert_nothing_raised { assert_select "div", /(foo|bar)/ } - assert_raises(Assertion) { assert_select "div", /foobar/ } + assert_raise(Assertion) { assert_select "div", /foobar/ } assert_nothing_raised { assert_select "div", :text=>/(foo|bar)/ } - assert_raises(Assertion) { assert_select "div", :text=>/foobar/ } - assert_raises(Assertion) { assert_select "p", :text=>/foobar/ } + assert_raise(Assertion) { assert_select "div", :text=>/foobar/ } + assert_raise(Assertion) { assert_select "p", :text=>/foobar/ } end def test_equality_of_html @@ -120,17 +120,17 @@ class AssertSelectTest < ActionController::TestCase text = "\"This is not a big problem,\" he said." html = "\"This is not a big problem,\" he said." assert_nothing_raised { assert_select "p", text } - assert_raises(Assertion) { assert_select "p", html } + assert_raise(Assertion) { assert_select "p", html } assert_nothing_raised { assert_select "p", :html=>html } - assert_raises(Assertion) { assert_select "p", :html=>text } + assert_raise(Assertion) { assert_select "p", :html=>text } # No stripping for pre. render_html %Q{
\n"This is not a big problem," he said.\n
} text = "\n\"This is not a big problem,\" he said.\n" html = "\n\"This is not a big problem,\" he said.\n" assert_nothing_raised { assert_select "pre", text } - assert_raises(Assertion) { assert_select "pre", html } + assert_raise(Assertion) { assert_select "pre", html } assert_nothing_raised { assert_select "pre", :html=>html } - assert_raises(Assertion) { assert_select "pre", :html=>text } + assert_raise(Assertion) { assert_select "pre", :html=>text } end def test_counts @@ -210,12 +210,12 @@ class AssertSelectTest < ActionController::TestCase assert_nothing_raised { assert_select "div", "bar" } assert_nothing_raised { assert_select "div", /\w*/ } assert_nothing_raised { assert_select "div", /\w*/, :count=>2 } - assert_raises(Assertion) { assert_select "div", :text=>"foo", :count=>2 } + assert_raise(Assertion) { assert_select "div", :text=>"foo", :count=>2 } assert_nothing_raised { assert_select "div", :html=>"bar" } assert_nothing_raised { assert_select "div", :html=>"bar" } assert_nothing_raised { assert_select "div", :html=>/\w*/ } assert_nothing_raised { assert_select "div", :html=>/\w*/, :count=>2 } - assert_raises(Assertion) { assert_select "div", :html=>"foo", :count=>2 } + assert_raise(Assertion) { assert_select "div", :html=>"foo", :count=>2 } end end @@ -253,7 +253,12 @@ class AssertSelectTest < ActionController::TestCase page.insert_html :top, "test1", "
foo
" page.insert_html :bottom, "test2", "
foo
" end - assert_raises(Assertion) {assert_select_rjs :insert, :top, "test2"} + assert_raise(Assertion) {assert_select_rjs :insert, :top, "test2"} + end + + def test_elect_with_xml_namespace_attributes + render_html %Q{} + assert_nothing_raised { assert_select "link[xlink:href=http://nowhere.com]" } end # @@ -331,7 +336,7 @@ class AssertSelectTest < ActionController::TestCase # Test that we fail if there is nothing to pick. def test_assert_select_rjs_fails_if_nothing_to_pick render_rjs { } - assert_raises(Assertion) { assert_select_rjs } + assert_raise(Assertion) { assert_select_rjs } end def test_assert_select_rjs_with_unicode @@ -346,10 +351,10 @@ class AssertSelectTest < ActionController::TestCase if str.respond_to?(:force_encoding) str.force_encoding(Encoding::UTF_8) assert_select str, /\343\203\201..\343\203\210/u - assert_raises(Assertion) { assert_select str, /\343\203\201.\343\203\210/u } + assert_raise(Assertion) { assert_select str, /\343\203\201.\343\203\210/u } else assert_select str, Regexp.new("\343\203\201..\343\203\210",0,'U') - assert_raises(Assertion) { assert_select str, Regexp.new("\343\203\201.\343\203\210",0,'U') } + assert_raise(Assertion) { assert_select str, Regexp.new("\343\203\201.\343\203\210",0,'U') } end end end @@ -373,7 +378,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#3" end - assert_raises(Assertion) { assert_select_rjs "test4" } + assert_raise(Assertion) { assert_select_rjs "test4" } end def test_assert_select_rjs_for_replace @@ -391,7 +396,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#1" end - assert_raises(Assertion) { assert_select_rjs :replace, "test2" } + assert_raise(Assertion) { assert_select_rjs :replace, "test2" } # Replace HTML. assert_select_rjs :replace_html do assert_select "div", 1 @@ -401,7 +406,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#2" end - assert_raises(Assertion) { assert_select_rjs :replace_html, "test1" } + assert_raise(Assertion) { assert_select_rjs :replace_html, "test1" } end def test_assert_select_rjs_for_chained_replace @@ -419,7 +424,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#1" end - assert_raises(Assertion) { assert_select_rjs :chained_replace, "test2" } + assert_raise(Assertion) { assert_select_rjs :chained_replace, "test2" } # Replace HTML. assert_select_rjs :chained_replace_html do assert_select "div", 1 @@ -429,7 +434,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#2" end - assert_raises(Assertion) { assert_select_rjs :replace_html, "test1" } + assert_raise(Assertion) { assert_select_rjs :replace_html, "test1" } end # Simple remove @@ -575,7 +580,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#3" end - assert_raises(Assertion) { assert_select_rjs :insert_html, "test1" } + assert_raise(Assertion) { assert_select_rjs :insert_html, "test1" } end # Positioned insert. @@ -608,8 +613,8 @@ class AssertSelectTest < ActionController::TestCase end def test_assert_select_rjs_raise_errors - assert_raises(ArgumentError) { assert_select_rjs(:destroy) } - assert_raises(ArgumentError) { assert_select_rjs(:insert, :left) } + assert_raise(ArgumentError) { assert_select_rjs(:destroy) } + assert_raise(ArgumentError) { assert_select_rjs(:insert, :left) } end # Simple selection from a single result. @@ -701,7 +706,7 @@ EOF # def test_assert_select_email - assert_raises(Assertion) { assert_select_email {} } + assert_raise(Assertion) { assert_select_email {} } AssertSelectMailer.deliver_test "

foo

bar

" assert_select_email do assert_select "div:root" do diff --git a/vendor/rails/actionpack/test/controller/caching_test.rb b/vendor/rails/actionpack/test/controller/caching_test.rb index 9af1ccc7..86dafd92 100644 --- a/vendor/rails/actionpack/test/controller/caching_test.rb +++ b/vendor/rails/actionpack/test/controller/caching_test.rb @@ -428,6 +428,20 @@ class ActionCacheTest < ActionController::TestCase assert_equal 'application/xml', @response.content_type end + def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key + # run it twice to cache it the first time + get :show, :format => 'xml' + get :show, :format => 'xml' + assert_equal 'application/xml', @response.content_type + end + + def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key_from_proc + # run it twice to cache it the first time + get :edit, :id => 1, :format => 'xml' + get :edit, :id => 1, :format => 'xml' + assert_equal 'application/xml', @response.content_type + end + def test_empty_path_is_normalized @mock_controller.mock_url_for = 'http://example.org/' @mock_controller.mock_path = '/' diff --git a/vendor/rails/actionpack/test/controller/fake_models.rb b/vendor/rails/actionpack/test/controller/fake_models.rb index 7420579e..0b30c79b 100644 --- a/vendor/rails/actionpack/test/controller/fake_models.rb +++ b/vendor/rails/actionpack/test/controller/fake_models.rb @@ -9,3 +9,11 @@ end class GoodCustomer < Customer end + +module Quiz + class Question < Struct.new(:name, :id) + def to_param + id.to_s + end + end +end diff --git a/vendor/rails/actionpack/test/controller/html-scanner/document_test.rb b/vendor/rails/actionpack/test/controller/html-scanner/document_test.rb index 1c3facb9..c68f04fa 100644 --- a/vendor/rails/actionpack/test/controller/html-scanner/document_test.rb +++ b/vendor/rails/actionpack/test/controller/html-scanner/document_test.rb @@ -134,7 +134,7 @@ HTML end def test_invalid_document_raises_exception_when_strict - assert_raises RuntimeError do + assert_raise RuntimeError do doc = HTML::Document.new(" diff --git a/vendor/rails/actionpack/test/controller/http_digest_authentication_test.rb b/vendor/rails/actionpack/test/controller/http_digest_authentication_test.rb index 4913e763..00789eea 100644 --- a/vendor/rails/actionpack/test/controller/http_digest_authentication_test.rb +++ b/vendor/rails/actionpack/test/controller/http_digest_authentication_test.rb @@ -5,7 +5,8 @@ class HttpDigestAuthenticationTest < ActionController::TestCase before_filter :authenticate, :only => :index before_filter :authenticate_with_request, :only => :display - USERS = { 'lifo' => 'world', 'pretty' => 'please' } + USERS = { 'lifo' => 'world', 'pretty' => 'please', + 'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))} def index render :text => "Hello Secret" @@ -107,8 +108,42 @@ class HttpDigestAuthenticationTest < ActionController::TestCase assert_equal 'Definitely Maybe', @response.body end - test "authentication request with relative URI" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "/", :username => 'pretty', :password => 'please') + test "authentication request with valid credential and nil session" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + + # session_id = "" in functional test, but is +nil+ in real life + @request.session.session_id = nil + get :display + + assert_response :success + assert assigns(:logged_in) + assert_equal 'Definitely Maybe', @response.body + end + + 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['REQUEST_URI'] = "/http_digest_authentication_test/dummy_digest/altered/uri" + get :display + + assert_response :unauthorized + assert_equal "Authentication Failed", @response.body + end + + test "authentication request with absolute uri" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/display", + :username => 'pretty', :password => 'please') + @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest/display" + get :display + + assert_response :success + assert assigns(:logged_in) + assert_equal 'Definitely Maybe', @response.body + end + + test "authentication request with password stored as ha1 digest hash" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'dhh', + :password => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":")), + :password_is_ha1 => true) get :display assert_response :success @@ -119,18 +154,22 @@ class HttpDigestAuthenticationTest < ActionController::TestCase private def encode_credentials(options) - options.reverse_merge!(:nc => "00000001", :cnonce => "0a4f113b") + options.reverse_merge!(:nc => "00000001", :cnonce => "0a4f113b", :password_is_ha1 => false) password = options.delete(:password) - # Perform unautheticated get to retrieve digest parameters to use on subsequent request + # Set in /initializers/session_store.rb. Used as secret in generating nonce + # to prevent tampering of timestamp + ActionController::Base.session_options[:secret] = "session_options_secret" + + # Perform unauthenticated GET to retrieve digest parameters to use on subsequent request get :index assert_response :unauthorized credentials = decode_credentials(@response.headers['WWW-Authenticate']) credentials.merge!(options) - credentials.reverse_merge!(:uri => "http://#{@request.host}#{@request.env['REQUEST_URI']}") - ActionController::HttpAuthentication::Digest.encode_credentials("GET", credentials, password) + credentials.reverse_merge!(:uri => "#{@request.env['REQUEST_URI']}") + ActionController::HttpAuthentication::Digest.encode_credentials("GET", credentials, password, options[:password_is_ha1]) end def decode_credentials(header) diff --git a/vendor/rails/actionpack/test/controller/integration_test.rb b/vendor/rails/actionpack/test/controller/integration_test.rb index b3f40fbe..e39a934c 100644 --- a/vendor/rails/actionpack/test/controller/integration_test.rb +++ b/vendor/rails/actionpack/test/controller/integration_test.rb @@ -2,7 +2,7 @@ require 'abstract_unit' class SessionTest < Test::Unit::TestCase StubApp = lambda { |env| - [200, {"Content-Type" => "text/html", "Content-Length" => "13"}, "Hello, World!"] + [200, {"Content-Type" => "text/html", "Content-Length" => "13"}, ["Hello, World!"]] } def setup @@ -389,9 +389,9 @@ class MetalTest < ActionController::IntegrationTest class Poller def self.call(env) if env["PATH_INFO"] =~ /^\/success/ - [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, "Hello World!"] + [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, ["Hello World!"]] else - [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, ''] + [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []] end end end diff --git a/vendor/rails/actionpack/test/controller/layout_test.rb b/vendor/rails/actionpack/test/controller/layout_test.rb index 28555ee3..1575674e 100644 --- a/vendor/rails/actionpack/test/controller/layout_test.rb +++ b/vendor/rails/actionpack/test/controller/layout_test.rb @@ -79,6 +79,10 @@ end class DefaultLayoutController < LayoutTest end +class AbsolutePathLayoutController < LayoutTest + layout File.expand_path(File.expand_path(__FILE__) + '/../../fixtures/layout_tests/layouts/layout_test.rhtml') +end + class HasOwnLayoutController < LayoutTest layout 'item' end @@ -137,12 +141,18 @@ class LayoutSetInResponseTest < ActionController::TestCase ensure ActionController::Base.exempt_from_layout.delete(/\.rhtml$/) end - + def test_layout_is_picked_from_the_controller_instances_view_path @controller = PrependsViewPathController.new get :hello assert_equal 'layouts/alt', @response.layout end + + def test_absolute_pathed_layout + @controller = AbsolutePathLayoutController.new + get :hello + assert_equal "layout_test.rhtml hello.rhtml", @response.body.strip + end end class RenderWithTemplateOptionController < LayoutTest diff --git a/vendor/rails/actionpack/test/controller/mime_responds_test.rb b/vendor/rails/actionpack/test/controller/mime_responds_test.rb index dc59180a..edd71623 100644 --- a/vendor/rails/actionpack/test/controller/mime_responds_test.rb +++ b/vendor/rails/actionpack/test/controller/mime_responds_test.rb @@ -469,7 +469,7 @@ class MimeControllerTest < ActionController::TestCase assert_equal '
Hello future from Firefox!
', @response.body @request.accept = "text/iphone" - assert_raises(ActionView::MissingTemplate) { get :iphone_with_html_response_type_without_layout } + assert_raise(ActionView::MissingTemplate) { get :iphone_with_html_response_type_without_layout } end end diff --git a/vendor/rails/actionpack/test/controller/polymorphic_routes_test.rb b/vendor/rails/actionpack/test/controller/polymorphic_routes_test.rb index 53295522..146d7036 100644 --- a/vendor/rails/actionpack/test/controller/polymorphic_routes_test.rb +++ b/vendor/rails/actionpack/test/controller/polymorphic_routes_test.rb @@ -18,6 +18,20 @@ class Tag < Article def response_id; 1 end end +class Tax + attr_reader :id + def save; @id = 1 end + def new_record?; @id.nil? end + def name + model = self.class.name.downcase + @id.nil? ? "new #{model}" : "#{model} ##{@id}" + end +end + +class Fax < Tax + def store_id; 1 end +end + # TODO: test nested models class Response::Nested < Response; end @@ -27,6 +41,8 @@ class PolymorphicRoutesTest < ActiveSupport::TestCase def setup @article = Article.new @response = Response.new + @tax = Tax.new + @fax = Fax.new end def test_with_record @@ -205,4 +221,73 @@ class PolymorphicRoutesTest < ActiveSupport::TestCase polymorphic_url(path) end end + + # Tests for names where .plural.singular doesn't round-trip + def test_with_irregular_plural_record + @tax.save + expects(:taxis_url).with(@tax) + polymorphic_url(@tax) + end + + def test_with_irregular_plural_new_record + expects(:taxes_url).with() + @tax.expects(:new_record?).returns(true) + polymorphic_url(@tax) + end + + def test_with_irregular_plural_record_and_action + expects(:new_taxis_url).with() + @tax.expects(:new_record?).never + polymorphic_url(@tax, :action => 'new') + end + + def test_irregular_plural_url_helper_prefixed_with_new + expects(:new_taxis_url).with() + new_polymorphic_url(@tax) + end + + def test_irregular_plural_url_helper_prefixed_with_edit + @tax.save + expects(:edit_taxis_url).with(@tax) + edit_polymorphic_url(@tax) + end + + def test_with_nested_irregular_plurals + @fax.save + expects(:taxis_faxis_url).with(@tax, @fax) + polymorphic_url([@tax, @fax]) + end + + def test_with_nested_unsaved_irregular_plurals + expects(:taxis_faxes_url).with(@tax) + polymorphic_url([@tax, @fax]) + end + + def test_new_with_irregular_plural_array_and_namespace + expects(:new_admin_taxis_url).with() + polymorphic_url([:admin, @tax], :action => 'new') + end + + def test_unsaved_with_irregular_plural_array_and_namespace + expects(:admin_taxes_url).with() + polymorphic_url([:admin, @tax]) + end + + def test_nesting_with_irregular_plurals_and_array_ending_in_singleton_resource + expects(:taxis_faxis_url).with(@tax) + polymorphic_url([@tax, :faxis]) + end + + def test_with_array_containing_single_irregular_plural_object + @tax.save + expects(:taxis_url).with(@tax) + polymorphic_url([nil, @tax]) + end + + def test_with_array_containing_single_name_irregular_plural + @tax.save + expects(:taxes_url) + polymorphic_url([:taxes]) + end + end diff --git a/vendor/rails/actionpack/test/controller/redirect_test.rb b/vendor/rails/actionpack/test/controller/redirect_test.rb index 27cedc91..91e21db8 100644 --- a/vendor/rails/actionpack/test/controller/redirect_test.rb +++ b/vendor/rails/actionpack/test/controller/redirect_test.rb @@ -212,7 +212,7 @@ class RedirectTest < ActionController::TestCase end def test_redirect_to_back_with_no_referer - assert_raises(ActionController::RedirectBackError) { + assert_raise(ActionController::RedirectBackError) { @request.env["HTTP_REFERER"] = nil get :redirect_to_back } @@ -239,7 +239,7 @@ class RedirectTest < ActionController::TestCase end def test_redirect_to_nil - assert_raises(ActionController::ActionControllerError) do + assert_raise(ActionController::ActionControllerError) do get :redirect_to_nil end end diff --git a/vendor/rails/actionpack/test/controller/render_test.rb b/vendor/rails/actionpack/test/controller/render_test.rb index 02ae8ac9..a5293156 100644 --- a/vendor/rails/actionpack/test/controller/render_test.rb +++ b/vendor/rails/actionpack/test/controller/render_test.rb @@ -157,6 +157,11 @@ class TestController < ActionController::Base render :file => 'test/dot.directory/render_file_with_ivar' end + def render_file_using_pathname + @secret = 'in the sauce' + render :file => Pathname.new(File.dirname(__FILE__)).join('..', 'fixtures', 'test', 'dot.directory', 'render_file_with_ivar.erb') + end + def render_file_from_template @secret = 'in the sauce' @path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')) @@ -313,6 +318,10 @@ class TestController < ActionController::Base def render_implicit_js_template_without_layout end + def render_html_explicit_template_and_layout + render :template => 'test/render_implicit_html_template_from_xhr_request', :layout => 'layouts/default_html' + end + def formatted_html_erb end @@ -678,6 +687,14 @@ class TestController < ActionController::Base render :partial => "hash_object", :object => {:first_name => "Sam"} end + def partial_with_nested_object + render :partial => "quiz/questions/question", :object => Quiz::Question.new("first") + end + + def partial_with_nested_object_shorthand + render Quiz::Question.new("first") + end + def partial_hash_collection render :partial => "hash_object", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ] end @@ -720,6 +737,8 @@ class TestController < ActionController::Base "delete_with_js", "update_page", "update_page_with_instance_variables" "layouts/standard" + when "render_implicit_js_template_without_layout" + "layouts/default_html" when "action_talk_to_layout", "layout_overriding_layout" "layouts/talk_from_action" when "render_implicit_html_template_from_xhr_request" @@ -817,6 +836,11 @@ class RenderTest < ActionController::TestCase assert_equal "hello world, I'm here!", @response.body end + def test_xhr_with_render_text_and_layout + xhr :get, :render_text_hello_world_with_layout + assert_equal "hello world, I'm here!", @response.body + end + def test_do_with_render_action_and_layout_false get :hello_world_with_layout_false assert_equal 'Hello world!', @response.body @@ -842,6 +866,11 @@ class RenderTest < ActionController::TestCase assert_equal "The secret is in the sauce\n", @response.body end + def test_render_file_using_pathname + get :render_file_using_pathname + assert_equal "The secret is in the sauce\n", @response.body + end + def test_render_file_with_locals get :render_file_with_locals assert_equal "The secret is in the sauce\n", @response.body @@ -918,11 +947,11 @@ class RenderTest < ActionController::TestCase end def test_attempt_to_access_object_method - assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone } + assert_raise(ActionController::UnknownAction, "No action responded to [clone]") { get :clone } end def test_private_methods - assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout } + assert_raise(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout } end def test_access_to_request_in_view @@ -1056,6 +1085,11 @@ class RenderTest < ActionController::TestCase assert_equal "XHR!\nHello HTML!", @response.body end + def test_should_render_explicit_html_template_with_html_layout + xhr :get, :render_html_explicit_template_and_layout + assert_equal "Hello HTML!\n", @response.body + end + def test_should_implicitly_render_js_template_without_layout get :render_implicit_js_template_without_layout, :format => :js assert_no_match //, @response.body @@ -1149,7 +1183,7 @@ class RenderTest < ActionController::TestCase end def test_bad_render_to_string_still_throws_exception - assert_raises(ActionView::MissingTemplate) { get :render_to_string_with_exception } + assert_raise(ActionView::MissingTemplate) { get :render_to_string_with_exception } end def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns @@ -1174,15 +1208,15 @@ class RenderTest < ActionController::TestCase end def test_double_render - assert_raises(ActionController::DoubleRenderError) { get :double_render } + assert_raise(ActionController::DoubleRenderError) { get :double_render } end def test_double_redirect - assert_raises(ActionController::DoubleRenderError) { get :double_redirect } + assert_raise(ActionController::DoubleRenderError) { get :double_redirect } end def test_render_and_redirect - assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect } + assert_raise(ActionController::DoubleRenderError) { get :render_and_redirect } end # specify the one exception to double render rule - render_to_string followed by render @@ -1463,6 +1497,16 @@ class RenderTest < ActionController::TestCase assert_equal "Sam\nmaS\n", @response.body end + def test_partial_with_nested_object + get :partial_with_nested_object + assert_equal "first", @response.body + end + + def test_partial_with_nested_object_shorthand + get :partial_with_nested_object_shorthand + assert_equal "first", @response.body + end + def test_hash_partial_collection get :partial_hash_collection assert_equal "Pratik\nkitarP\nAmy\nymA\n", @response.body @@ -1481,7 +1525,7 @@ class RenderTest < ActionController::TestCase end def test_render_missing_partial_template - assert_raises(ActionView::MissingTemplate) do + assert_raise(ActionView::MissingTemplate) do get :missing_partial end end @@ -1509,7 +1553,7 @@ class ExpiresInRenderTest < ActionController::TestCase assert_equal "max-age=60, private", @response.headers["Cache-Control"] end - def test_expires_in_header + def test_expires_in_header_with_public get :conditional_hello_with_expires_in_with_public assert_equal "max-age=60, public", @response.headers["Cache-Control"] end diff --git a/vendor/rails/actionpack/test/controller/request/multipart_params_parsing_test.rb b/vendor/rails/actionpack/test/controller/request/multipart_params_parsing_test.rb index 054519d0..b812072e 100644 --- a/vendor/rails/actionpack/test/controller/request/multipart_params_parsing_test.rb +++ b/vendor/rails/actionpack/test/controller/request/multipart_params_parsing_test.rb @@ -103,7 +103,7 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest test "does not create tempfile if no file has been selected" do params = parse_multipart('none') - assert_equal %w(files submit-name), params.keys.sort + assert_equal %w(submit-name), params.keys.sort assert_equal 'Larry', params['submit-name'] assert_equal nil, params['files'] end diff --git a/vendor/rails/actionpack/test/controller/request_forgery_protection_test.rb b/vendor/rails/actionpack/test/controller/request_forgery_protection_test.rb index ef0bf5fd..835e73e3 100644 --- a/vendor/rails/actionpack/test/controller/request_forgery_protection_test.rb +++ b/vendor/rails/actionpack/test/controller/request_forgery_protection_test.rb @@ -79,17 +79,17 @@ module RequestForgeryProtectionTests def test_should_not_allow_html_post_without_token @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - assert_raises(ActionController::InvalidAuthenticityToken) { post :index, :format => :html } + assert_raise(ActionController::InvalidAuthenticityToken) { post :index, :format => :html } end def test_should_not_allow_html_put_without_token @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - assert_raises(ActionController::InvalidAuthenticityToken) { put :index, :format => :html } + assert_raise(ActionController::InvalidAuthenticityToken) { put :index, :format => :html } end def test_should_not_allow_html_delete_without_token @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - assert_raises(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html } + assert_raise(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html } end def test_should_allow_api_formatted_post_without_token @@ -111,42 +111,42 @@ module RequestForgeryProtectionTests end def test_should_not_allow_api_formatted_post_sent_as_url_encoded_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s post :index, :format => 'xml' end end def test_should_not_allow_api_formatted_put_sent_as_url_encoded_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s put :index, :format => 'xml' end end def test_should_not_allow_api_formatted_delete_sent_as_url_encoded_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s delete :index, :format => 'xml' end end def test_should_not_allow_api_formatted_post_sent_as_multipart_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s post :index, :format => 'xml' end end def test_should_not_allow_api_formatted_put_sent_as_multipart_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s put :index, :format => 'xml' end end def test_should_not_allow_api_formatted_delete_sent_as_multipart_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s delete :index, :format => 'xml' end diff --git a/vendor/rails/actionpack/test/controller/request_test.rb b/vendor/rails/actionpack/test/controller/request_test.rb index efe4f136..c72f885a 100644 --- a/vendor/rails/actionpack/test/controller/request_test.rb +++ b/vendor/rails/actionpack/test/controller/request_test.rb @@ -59,7 +59,7 @@ class RequestTest < ActiveSupport::TestCase assert_equal '3.4.5.6', @request.remote_ip @request.env['HTTP_CLIENT_IP'] = '8.8.8.8' - e = assert_raises(ActionController::ActionControllerError) { + e = assert_raise(ActionController::ActionControllerError) { @request.remote_ip } assert_match /IP spoofing attack/, e.message @@ -297,7 +297,7 @@ class RequestTest < ActiveSupport::TestCase end def test_invalid_http_method_raises_exception - assert_raises(ActionController::UnknownHttpMethod) do + assert_raise(ActionController::UnknownHttpMethod) do self.request_method = :random_method @request.request_method end @@ -311,7 +311,7 @@ class RequestTest < ActiveSupport::TestCase end def test_invalid_method_hacking_on_post_raises_exception - assert_raises(ActionController::UnknownHttpMethod) do + assert_raise(ActionController::UnknownHttpMethod) do self.request_method = :_random_method @request.request_method end diff --git a/vendor/rails/actionpack/test/controller/resources_test.rb b/vendor/rails/actionpack/test/controller/resources_test.rb index c441cfd4..c807e71c 100644 --- a/vendor/rails/actionpack/test/controller/resources_test.rb +++ b/vendor/rails/actionpack/test/controller/resources_test.rb @@ -99,7 +99,7 @@ class ResourcesTest < ActionController::TestCase expected_options = {:controller => 'messages', :action => 'show', :id => '1.1.1'} with_restful_routing :messages do - assert_raises(ActionController::RoutingError) do + assert_raise(ActionController::RoutingError) do assert_recognizes(expected_options, :path => 'messages/1.1.1', :method => :get) end end @@ -175,6 +175,24 @@ class ResourcesTest < ActionController::TestCase end end + def test_with_collection_actions_and_name_prefix_and_member_action_with_same_name + actions = { 'a' => :get } + + with_restful_routing :messages, :path_prefix => '/threads/:thread_id', :name_prefix => "thread_", :collection => actions, :member => actions do + assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + actions.each do |action, method| + assert_recognizes(options.merge(:action => action), :path => "/threads/1/messages/#{action}", :method => method) + end + end + + assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + actions.keys.each do |action| + assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action + end + end + end + end + def test_with_collection_action_and_name_prefix_and_formatted actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete } @@ -209,6 +227,14 @@ class ResourcesTest < ActionController::TestCase end end + def test_with_member_action_and_requirement + expected_options = {:controller => 'messages', :action => 'mark', :id => '1.1.1'} + + with_restful_routing(:messages, :requirements => {:id => /[0-9]\.[0-9]\.[0-9]/}, :member => { :mark => :get }) do + assert_recognizes(expected_options, :path => 'messages/1.1.1/mark', :method => :get) + end + end + def test_member_when_override_paths_for_default_restful_actions_with [:put, :post].each do |method| with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do @@ -325,7 +351,7 @@ class ResourcesTest < ActionController::TestCase with_restful_routing :messages do assert_restful_routes_for :messages do |options| assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get) - assert_raises(ActionController::MethodNotAllowed) do + assert_raise(ActionController::MethodNotAllowed) do ActionController::Routing::Routes.recognize_path("/messages/new", :method => :post) end end @@ -406,6 +432,34 @@ class ResourcesTest < ActionController::TestCase end end + def test_shallow_nested_restful_routes_with_namespaces + with_routing do |set| + set.draw do |map| + map.namespace :backoffice do |map| + map.namespace :admin do |map| + map.resources :products, :shallow => true do |map| + map.resources :images + end + end + end + end + + assert_simply_restful_for :products, + :controller => 'backoffice/admin/products', + :namespace => 'backoffice/admin/', + :name_prefix => 'backoffice_admin_', + :path_prefix => 'backoffice/admin/', + :shallow => true + assert_simply_restful_for :images, + :controller => 'backoffice/admin/images', + :namespace => 'backoffice/admin/', + :name_prefix => 'backoffice_admin_product_', + :path_prefix => 'backoffice/admin/products/1/', + :shallow => true, + :options => { :product_id => '1' } + end + end + def test_restful_routes_dont_generate_duplicates with_restful_routing :messages do routes = ActionController::Routing::Routes.routes @@ -583,11 +637,11 @@ class ResourcesTest < ActionController::TestCase options = { :controller => controller_name.to_s } collection_path = "/#{controller_name}" - assert_raises(ActionController::MethodNotAllowed) do + assert_raise(ActionController::MethodNotAllowed) do assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put) end - assert_raises(ActionController::MethodNotAllowed) do + assert_raise(ActionController::MethodNotAllowed) do assert_recognizes(options.merge(:action => 'destroy'), :path => collection_path, :method => :delete) end end @@ -596,7 +650,7 @@ class ResourcesTest < ActionController::TestCase def test_should_not_allow_invalid_head_method_for_member_routes with_routing do |set| set.draw do |map| - assert_raises(ArgumentError) do + assert_raise(ArgumentError) do map.resources :messages, :member => {:something => :head} end end @@ -606,7 +660,7 @@ class ResourcesTest < ActionController::TestCase def test_should_not_allow_invalid_http_methods_for_member_routes with_routing do |set| set.draw do |map| - assert_raises(ArgumentError) do + assert_raise(ArgumentError) do map.resources :messages, :member => {:something => :invalid} end end @@ -1074,7 +1128,7 @@ class ResourcesTest < ActionController::TestCase path = "#{options[:as] || controller_name}" collection_path = "/#{options[:path_prefix]}#{path}" - shallow_path = "/#{options[:path_prefix] unless options[:shallow]}#{path}" + shallow_path = "/#{options[:shallow] ? options[:namespace] : options[:path_prefix]}#{path}" member_path = "#{shallow_path}/1" new_path = "#{collection_path}/#{new_action}" edit_member_path = "#{member_path}/#{edit_action}" @@ -1138,10 +1192,10 @@ class ResourcesTest < ActionController::TestCase options[:options].delete :action path = "#{options[:as] || controller_name}" - shallow_path = "/#{options[:path_prefix] unless options[:shallow]}#{path}" + shallow_path = "/#{options[:shallow] ? options[:namespace] : options[:path_prefix]}#{path}" full_path = "/#{options[:path_prefix]}#{path}" name_prefix = options[:name_prefix] - shallow_prefix = "#{options[:name_prefix] unless options[:shallow]}" + shallow_prefix = options[:shallow] ? options[:namespace].try(:gsub, /\//, '_') : options[:name_prefix] new_action = "new" edit_action = "edit" diff --git a/vendor/rails/actionpack/test/controller/routing_test.rb b/vendor/rails/actionpack/test/controller/routing_test.rb index 01b3db64..ef561197 100644 --- a/vendor/rails/actionpack/test/controller/routing_test.rb +++ b/vendor/rails/actionpack/test/controller/routing_test.rb @@ -219,7 +219,7 @@ class DynamicSegmentTest < Test::Unit::TestCase a_value = nil # Local jump because of return inside eval. - assert_raises(LocalJumpError) { eval(segment.extraction_code) } + assert_raise(LocalJumpError) { eval(segment.extraction_code) } end def test_extraction_code_should_return_on_mismatch @@ -229,7 +229,7 @@ class DynamicSegmentTest < Test::Unit::TestCase a_value = nil # Local jump because of return inside eval. - assert_raises(LocalJumpError) { eval(segment.extraction_code) } + assert_raise(LocalJumpError) { eval(segment.extraction_code) } end def test_extraction_code_should_accept_value_and_set_local @@ -494,7 +494,7 @@ class RouteBuilderTest < Test::Unit::TestCase defaults = {:action => 'buy', :person => nil, :car => nil} requirements = {:person => /\w+/, :car => /^\w+$/} - assert_raises ArgumentError do + assert_raise ArgumentError do route_requirements = builder.assign_route_options(segments, defaults, requirements) end @@ -882,7 +882,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase 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")) - assert_raises(ActionController::RoutingError) { rs.recognize_path("/admin/products") } + assert_raise(ActionController::RoutingError) { rs.recognize_path("/admin/products") } end def test_route_with_regexp_and_dot @@ -955,6 +955,13 @@ class LegacyRouteSetTests < Test::Unit::TestCase x.send(:page_url)) end + def test_named_route_with_blank_path_prefix + rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :path_prefix => '' + x = setup_for_named_route + assert_equal("http://test.host/page", + x.send(:page_url)) + end + def test_named_route_with_nested_controller rs.add_named_route :users, 'admin/user', :controller => 'admin/user', :action => 'index' x = setup_for_named_route @@ -1060,11 +1067,11 @@ class LegacyRouteSetTests < Test::Unit::TestCase rs.draw do |map| map.connect ':controller/:action/:id' end - assert_raises(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") } + assert_raise(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") } end def test_paths_do_not_accept_defaults - assert_raises(ActionController::RoutingError) do + assert_raise(ActionController::RoutingError) do rs.draw do |map| map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default) map.connect ':controller/:action/:id' @@ -1197,7 +1204,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase assert_equal '/post/10', rs.generate(:controller => 'post', :action => 'show', :id => 10) - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do rs.generate(:controller => 'post', :action => 'show') end end @@ -1407,7 +1414,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase end x = setup_for_named_route - assert_raises(ActionController::RoutingError) do + assert_raise(ActionController::RoutingError) do x.send(:foo_with_requirement_url, "I am Against the requirements") end end @@ -1539,7 +1546,7 @@ class RouteTest < Test::Unit::TestCase end def test_builder_complains_without_controller - assert_raises(ArgumentError) do + assert_raise(ArgumentError) do ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index" end end @@ -1822,27 +1829,27 @@ class RouteSetTest < Test::Unit::TestCase end def test_route_requirements_with_anchor_chars_are_invalid - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /^\d+/ end end - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\A\d+/ end end - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+$/ end end - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\Z/ end end - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\z/ end @@ -1851,22 +1858,30 @@ class RouteSetTest < Test::Unit::TestCase set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/, :name => /^(david|jamis)/ end - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.generate :controller => 'pages', :action => 'show', :id => 10 end end end def test_route_requirements_with_invalid_http_method_is_invalid - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'valid/route', :controller => 'pages', :action => 'show', :conditions => {:method => :invalid} end end end + def test_route_requirements_with_options_method_condition_is_valid + assert_nothing_raised do + set.draw do |map| + map.connect 'valid/route', :controller => 'pages', :action => 'show', :conditions => {:method => :options} + end + end + end + def test_route_requirements_with_head_method_condition_is_invalid - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'valid/route', :controller => 'pages', :action => 'show', :conditions => {:method => :head} end @@ -1878,10 +1893,10 @@ class RouteSetTest < Test::Unit::TestCase map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/ end assert_equal '/page/37s', set.generate(:controller => 'pages', :action => 'show', :name => 'jamis') - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.generate(:controller => 'pages', :action => 'show', :name => 'not_jamis') end - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.generate(:controller => 'pages', :action => 'show', :name => 'nor_jamis_and_david') end end @@ -1924,7 +1939,7 @@ class RouteSetTest < Test::Unit::TestCase assert_equal("update", request.path_parameters[:action]) request.recycle! - assert_raises(ActionController::UnknownHttpMethod) { + assert_raise(ActionController::UnknownHttpMethod) { request.env["REQUEST_METHOD"] = "BACON" set.recognize(request) } @@ -2122,11 +2137,9 @@ class RouteSetTest < Test::Unit::TestCase Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) }) set.draw do |map| - map.namespace 'api', :path_prefix => 'prefix' do |api| api.route 'inventory', :controller => "products", :action => 'inventory' end - end request.path = "/prefix/inventory" @@ -2138,6 +2151,24 @@ class RouteSetTest < Test::Unit::TestCase Object.send(:remove_const, :Api) end + def test_namespace_with_blank_path_prefix + Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) }) + + set.draw do |map| + map.namespace 'api', :path_prefix => '' do |api| + api.route 'inventory', :controller => "products", :action => 'inventory' + end + end + + request.path = "/inventory" + request.env["REQUEST_METHOD"] = "GET" + assert_nothing_raised { set.recognize(request) } + assert_equal("api/products", request.path_parameters[:controller]) + assert_equal("inventory", request.path_parameters[:action]) + ensure + Object.send(:remove_const, :Api) + end + def test_generate_finds_best_fit set.draw do |map| map.connect "/people", :controller => "people", :action => "index" @@ -2202,6 +2233,13 @@ class RouteSetTest < Test::Unit::TestCase assert_equal "/my/foo/bar/7?x=y", set.generate(args) end + def test_generate_with_blank_path_prefix + set.draw { |map| map.connect ':controller/:action/:id', :path_prefix => '' } + + args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" } + assert_equal "/foo/bar/7?x=y", set.generate(args) + end + def test_named_routes_are_never_relative_to_modules set.draw do |map| map.connect "/connection/manage/:action", :controller => 'connection/manage' @@ -2309,7 +2347,7 @@ class RouteSetTest < Test::Unit::TestCase end def test_route_requirements_with_unsupported_regexp_options_must_error - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:name', :controller => 'pages', :action => 'show', @@ -2347,7 +2385,7 @@ class RouteSetTest < Test::Unit::TestCase :requirements => {:name => /(david|jamis)/i} end assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis')) - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.recognize_path('/page/davidjamis') end assert_equal({:controller => 'pages', :action => 'show', :name => 'DAVID'}, set.recognize_path('/page/DAVID')) @@ -2361,7 +2399,7 @@ class RouteSetTest < Test::Unit::TestCase end url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) assert_equal "/page/david", url - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) end url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) @@ -2381,10 +2419,10 @@ class RouteSetTest < Test::Unit::TestCase end assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis')) assert_equal({:controller => 'pages', :action => 'show', :name => 'david'}, set.recognize_path('/page/david')) - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.recognize_path('/page/david #The Creator') end - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.recognize_path('/page/David') end end @@ -2402,10 +2440,10 @@ class RouteSetTest < Test::Unit::TestCase end url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) assert_equal "/page/david", url - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) end - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) end end diff --git a/vendor/rails/actionpack/test/controller/selector_test.rb b/vendor/rails/actionpack/test/controller/selector_test.rb index 4e31b457..9d0613d1 100644 --- a/vendor/rails/actionpack/test/controller/selector_test.rb +++ b/vendor/rails/actionpack/test/controller/selector_test.rb @@ -303,7 +303,7 @@ class SelectorTest < Test::Unit::TestCase assert_equal 1, @matches.size assert_equal "2", @matches[0].attributes["id"] # Before first and past last returns nothing.: - assert_raises(ArgumentError) { select("tr:nth-child(-1)") } + assert_raise(ArgumentError) { select("tr:nth-child(-1)") } select("tr:nth-child(0)") assert_equal 0, @matches.size select("tr:nth-child(5)") @@ -597,8 +597,8 @@ class SelectorTest < Test::Unit::TestCase def test_negation_details parse(%Q{

}) - assert_raises(ArgumentError) { select(":not(") } - assert_raises(ArgumentError) { select(":not(:not())") } + assert_raise(ArgumentError) { select(":not(") } + assert_raise(ArgumentError) { select(":not(:not())") } select("p:not(#1):not(#3)") assert_equal 1, @matches.size assert_equal "2", @matches[0].attributes["id"] diff --git a/vendor/rails/actionpack/test/controller/send_file_test.rb b/vendor/rails/actionpack/test/controller/send_file_test.rb index 5fc79baa..a27e9519 100644 --- a/vendor/rails/actionpack/test/controller/send_file_test.rb +++ b/vendor/rails/actionpack/test/controller/send_file_test.rb @@ -142,7 +142,7 @@ class SendFileTest < ActionController::TestCase } @controller.headers = {} - assert_raises(ArgumentError){ @controller.send(:send_file_headers!, options) } + assert_raise(ArgumentError){ @controller.send(:send_file_headers!, options) } end %w(file data).each do |method| diff --git a/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb b/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb index 9c93ca65..48a961ca 100644 --- a/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb +++ b/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb @@ -199,29 +199,18 @@ class CookieStoreTest < ActionController::IntegrationTest with_test_route_set do # First request accesses the session - time = Time.local(2008, 4, 24) - Time.stubs(:now).returns(time) - expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") - cookies[SessionKey] = SignedBar get '/set_session_value' assert_response :success + cookie = headers['Set-Cookie'] - cookie_body = response.body - assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", - headers['Set-Cookie'] - - # Second request does not access the session - time = Time.local(2008, 4, 25) - Time.stubs(:now).returns(time) - expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") - + # Second request does not access the session so the + # expires header should not be changed get '/no_session_access' assert_response :success - - assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", - headers['Set-Cookie'] + assert_equal cookie, headers['Set-Cookie'], + "#{unmarshal_session(cookie).inspect} expected but was #{unmarshal_session(headers['Set-Cookie']).inspect}" end end @@ -236,4 +225,13 @@ class CookieStoreTest < ActionController::IntegrationTest yield end end + + def unmarshal_session(cookie_string) + session = Rack::Utils.parse_query(cookie_string, ';,').inject({}) {|h,(k,v)| + h[k] = Array === v ? v.first : v + h + }[SessionKey] + verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1') + verifier.verify(session) + end end diff --git a/vendor/rails/actionpack/test/controller/session/mem_cache_store_test.rb b/vendor/rails/actionpack/test/controller/session/mem_cache_store_test.rb index c3a6c8ce..2f80a3c7 100644 --- a/vendor/rails/actionpack/test/controller/session/mem_cache_store_test.rb +++ b/vendor/rails/actionpack/test/controller/session/mem_cache_store_test.rb @@ -17,11 +17,14 @@ class MemCacheStoreTest < ActionController::IntegrationTest end def get_session_id - render :text => "foo: #{session[:foo].inspect}; id: #{request.session_options[:id]}" + session[:foo] + render :text => "#{request.session_options[:id]}" end def call_reset_session + session[:bar] reset_session + session[:bar] = "baz" head :ok end @@ -58,6 +61,27 @@ class MemCacheStoreTest < ActionController::IntegrationTest 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' @@ -67,7 +91,7 @@ class MemCacheStoreTest < ActionController::IntegrationTest get '/get_session_id' assert_response :success - assert_equal "foo: \"bar\"; id: #{session_id}", response.body + assert_equal session_id, response.body end end @@ -85,22 +109,6 @@ class MemCacheStoreTest < ActionController::IntegrationTest assert_equal nil, cookies['_session_id'] 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'] - - 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 - end - end rescue LoadError, RuntimeError $stderr.puts "Skipping MemCacheStoreTest tests. Start memcached and try again." end diff --git a/vendor/rails/actionpack/test/controller/url_rewriter_test.rb b/vendor/rails/actionpack/test/controller/url_rewriter_test.rb index 09a8356f..863f8414 100644 --- a/vendor/rails/actionpack/test/controller/url_rewriter_test.rb +++ b/vendor/rails/actionpack/test/controller/url_rewriter_test.rb @@ -99,7 +99,7 @@ class UrlWriterTests < ActionController::TestCase end def test_exception_is_thrown_without_host - assert_raises RuntimeError do + assert_raise RuntimeError do W.new.url_for :controller => 'c', :action => 'a', :id => 'i' end end diff --git a/vendor/rails/actionpack/test/fixtures/layouts/default_html.html.erb b/vendor/rails/actionpack/test/fixtures/layouts/default_html.html.erb new file mode 100644 index 00000000..edd71911 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/layouts/default_html.html.erb @@ -0,0 +1 @@ +<%= @content_for_layout %> diff --git a/vendor/rails/actionpack/test/fixtures/quiz/questions/_question.html.erb b/vendor/rails/actionpack/test/fixtures/quiz/questions/_question.html.erb new file mode 100644 index 00000000..fb4dcfee --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/quiz/questions/_question.html.erb @@ -0,0 +1 @@ +<%= question.name %> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/template/active_record_helper_test.rb b/vendor/rails/actionpack/test/template/active_record_helper_test.rb index e46f95d1..83c028b5 100644 --- a/vendor/rails/actionpack/test/template/active_record_helper_test.rb +++ b/vendor/rails/actionpack/test/template/active_record_helper_test.rb @@ -19,6 +19,30 @@ class ActiveRecordHelperTest < ActionView::TestCase Column = Struct.new("Column", :type, :name, :human_name) end + class DirtyPost + class Errors + def empty? + false + end + + def count + 1 + end + + def full_messages + ["Author name can't be empty"] + end + + def on(field) + "can't be empty" + end + end + + def errors + Errors.new + end + end + def setup_post @post = Post.new def @post.errors @@ -195,10 +219,20 @@ class ActiveRecordHelperTest < ActionView::TestCase assert_equal %(

1 error prohibited this post from being saved

There were problems with the following fields:

  • Author name can't be empty
), error_messages_for("post", :class => "errorDeathByClass", :id => nil, :header_tag => "h1") end + def test_error_messages_for_escapes_html + @dirty_post = DirtyPost.new + assert_dom_equal %(

1 error prohibited this dirty post from being saved

There were problems with the following fields:

  • Author name can't be <em>empty</em>
), error_messages_for("dirty_post") + end + def test_error_messages_for_handles_nil assert_equal "", error_messages_for("notthere") end + def test_error_message_on_escapes_html + @dirty_post = DirtyPost.new + assert_dom_equal "
can't be <em>empty</em>
", error_message_on(:dirty_post, :author_name) + end + def test_error_message_on_handles_nil assert_equal "", error_message_on("notthere", "notthere") end diff --git a/vendor/rails/actionpack/test/template/form_tag_helper_test.rb b/vendor/rails/actionpack/test/template/form_tag_helper_test.rb index 0c8af60a..c713b8da 100644 --- a/vendor/rails/actionpack/test/template/form_tag_helper_test.rb +++ b/vendor/rails/actionpack/test/template/form_tag_helper_test.rb @@ -266,11 +266,18 @@ class FormTagHelperTest < ActionView::TestCase def test_submit_tag_with_confirmation assert_dom_equal( - %(), + %(), submit_tag("Save", :confirm => "Are you sure?") ) end - + + def test_submit_tag_with_confirmation_and_with_disable_with + assert_dom_equal( + %(), + submit_tag("Save", :disable_with => "Saving...", :confirm => "Are you sure?") + ) + end + def test_image_submit_tag_with_confirmation assert_dom_equal( %(), diff --git a/vendor/rails/actionpack/test/template/javascript_helper_test.rb b/vendor/rails/actionpack/test/template/javascript_helper_test.rb index d4111112..d2fb24e3 100644 --- a/vendor/rails/actionpack/test/template/javascript_helper_test.rb +++ b/vendor/rails/actionpack/test/template/javascript_helper_test.rb @@ -45,11 +45,6 @@ class JavaScriptHelperTest < ActionView::TestCase link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/') end - def test_link_to_function_with_href - assert_dom_equal %(Greeting), - link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/') - end - def test_button_to_function assert_dom_equal %(), button_to_function("Greeting", "alert('Hello world!')") diff --git a/vendor/rails/actionpack/test/template/number_helper_test.rb b/vendor/rails/actionpack/test/template/number_helper_test.rb index 9c9f5493..29cb60fd 100644 --- a/vendor/rails/actionpack/test/template/number_helper_test.rb +++ b/vendor/rails/actionpack/test/template/number_helper_test.rb @@ -4,6 +4,7 @@ class NumberHelperTest < ActionView::TestCase tests ActionView::Helpers::NumberHelper def test_number_to_phone + assert_equal("555-1234", number_to_phone(5551234)) assert_equal("800-555-1212", number_to_phone(8005551212)) assert_equal("(800) 555-1212", number_to_phone(8005551212, {:area_code => true})) assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "})) diff --git a/vendor/rails/actionpack/test/template/render_test.rb b/vendor/rails/actionpack/test/template/render_test.rb index b042eb3d..9adf053b 100644 --- a/vendor/rails/actionpack/test/template/render_test.rb +++ b/vendor/rails/actionpack/test/template/render_test.rb @@ -145,6 +145,10 @@ module RenderTestCases assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name end + def test_render_object + assert_equal "Hello: david", @view.render(:partial => "test/customer", :object => Customer.new("david")) + end + def test_render_partial_collection assert_equal "Hello: davidHello: mary", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ]) end diff --git a/vendor/rails/actionpack/test/template/text_helper_test.rb b/vendor/rails/actionpack/test/template/text_helper_test.rb index 56484577..a370f145 100644 --- a/vendor/rails/actionpack/test/template/text_helper_test.rb +++ b/vendor/rails/actionpack/test/template/text_helper_test.rb @@ -375,6 +375,12 @@ class TextHelperTest < ActionView::TestCase assert_equal "{link: #{link3_result}}", auto_link("{link: #{link3_raw}}") end + def test_auto_link_in_tags + link_raw = 'http://www.rubyonrails.org/images/rails.png' + link_result = %Q() + assert_equal link_result, auto_link(link_result) + end + def test_auto_link_at_eol url1 = "http://api.rubyonrails.com/Foo.html" url2 = "http://www.ruby-doc.org/core/Bar.html" diff --git a/vendor/rails/actionpack/test/template/url_helper_test.rb b/vendor/rails/actionpack/test/template/url_helper_test.rb index e7799fb2..5900709d 100644 --- a/vendor/rails/actionpack/test/template/url_helper_test.rb +++ b/vendor/rails/actionpack/test/template/url_helper_test.rb @@ -220,7 +220,7 @@ class UrlHelperTest < ActionView::TestCase end def test_link_tag_using_post_javascript_and_popup - assert_raises(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") } + assert_raise(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") } end def test_link_tag_using_block_in_erb diff --git a/vendor/rails/activemodel/lib/active_model/validations/inclusion.rb b/vendor/rails/activemodel/lib/active_model/validations/inclusion.rb index 9b4cb643..f288810d 100644 --- a/vendor/rails/activemodel/lib/active_model/validations/inclusion.rb +++ b/vendor/rails/activemodel/lib/active_model/validations/inclusion.rb @@ -4,7 +4,7 @@ module ActiveModel # 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 ), :message => "woah! what are you then!??!!" + # validates_inclusion_of :gender, :in => %w( m f ) # validates_inclusion_of :age, :in => 0..99 # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %s is not included in the list" # end diff --git a/vendor/rails/activemodel/test/state_machine/event_test.rb b/vendor/rails/activemodel/test/state_machine/event_test.rb index 8fb7e82e..64dc8c48 100644 --- a/vendor/rails/activemodel/test/state_machine/event_test.rb +++ b/vendor/rails/activemodel/test/state_machine/event_test.rb @@ -31,7 +31,7 @@ class EventBeingFiredTest < ActiveModel::TestCase test 'should raise an AASM::InvalidTransition error if the transitions are empty' do event = ActiveModel::StateMachine::Event.new(nil, :event) - assert_raises ActiveModel::StateMachine::InvalidTransition do + assert_raise ActiveModel::StateMachine::InvalidTransition do event.fire(nil) end end diff --git a/vendor/rails/activerecord/CHANGELOG b/vendor/rails/activerecord/CHANGELOG index 404481ea..c73ac464 100644 --- a/vendor/rails/activerecord/CHANGELOG +++ b/vendor/rails/activerecord/CHANGELOG @@ -1,12 +1,9 @@ -*2.3.1 [RC2] (March 5, 2009)* +*2.3.2 [Final] (March 15, 2009)* -* Added ActiveRecord::Base.each and ActiveRecord::Base.find_in_batches for batch processing [DHH/Jamis Buck] +* 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] - -*2.3.0 [RC1] (February 1st, 2009)* - * Add Support for updating deeply nested models from a single form. #1202 [Eloy Duran] class Book < ActiveRecord::Base diff --git a/vendor/rails/activerecord/Rakefile b/vendor/rails/activerecord/Rakefile index aec9b382..b50008c9 100644 --- a/vendor/rails/activerecord/Rakefile +++ b/vendor/rails/activerecord/Rakefile @@ -177,7 +177,7 @@ spec = Gem::Specification.new do |s| s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) } end - s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD) s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite" s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite" diff --git a/vendor/rails/activerecord/lib/active_record/associations.rb b/vendor/rails/activerecord/lib/active_record/associations.rb index 6e88c897..6d25b36a 100755 --- a/vendor/rails/activerecord/lib/active_record/associations.rb +++ b/vendor/rails/activerecord/lib/active_record/associations.rb @@ -51,6 +51,12 @@ module ActiveRecord end end + class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc: + def initialize(reflection) + super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.") + end + end + class EagerLoadPolymorphicError < ActiveRecordError #:nodoc: def initialize(reflection) super("Can not eagerly load the polymorphic association #{reflection.name.inspect}") @@ -65,7 +71,7 @@ module ActiveRecord # See ActiveRecord::Associations::ClassMethods for documentation. module Associations # :nodoc: - # These classes will be loaded when associatoins are created. + # These classes will be loaded when associations are created. # So there is no need to eager load them. autoload :AssociationCollection, 'active_record/associations/association_collection' autoload :AssociationProxy, 'active_record/associations/association_proxy' @@ -997,9 +1003,7 @@ module ActiveRecord # Create the callbacks to update counter cache if options[:counter_cache] - cache_column = options[:counter_cache] == true ? - "#{self.to_s.demodulize.underscore.pluralize}_count" : - options[:counter_cache] + cache_column = reflection.counter_cache_column method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym define_method(method_name) do @@ -1526,6 +1530,10 @@ module ActiveRecord options[:extend] = create_extension_modules(association_id, extension, options[:extend]) reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self) + + if reflection.association_foreign_key == reflection.primary_key_name + raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection) + end reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name)) @@ -1848,9 +1856,10 @@ module ActiveRecord def construct(parent, associations, joins, row) case associations when Symbol, String - while (join = joins.shift).reflection.name.to_s != associations.to_s - raise ConfigurationError, "Not Enough Associations" if joins.empty? - end + join = joins.detect{|j| j.reflection.name.to_s == associations.to_s && j.parent_table_name == parent.class.table_name } + raise(ConfigurationError, "No such association") if join.nil? + + joins.delete(join) construct_association(parent, join, row) when Array associations.each do |association| @@ -1858,7 +1867,11 @@ module ActiveRecord end when Hash associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name| - association = construct_association(parent, joins.shift, row) + join = joins.detect{|j| j.reflection.name.to_s == name.to_s && j.parent_table_name == parent.class.table_name } + raise(ConfigurationError, "No such association") if join.nil? + + association = construct_association(parent, join, row) + joins.delete(join) construct(association, associations[name], joins, row) if association end else diff --git a/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb b/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb index 0fefec12..3aef1b21 100644 --- a/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb +++ b/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb @@ -60,7 +60,7 @@ module ActiveRecord @reflection.klass.find(*args) end end - + # Fetches the first one using SQL if possible. def first(*args) if fetch_first_or_last_using_find?(args) @@ -143,6 +143,8 @@ module ActiveRecord end # Remove all records from this association + # + # See delete for more info. def delete_all load_target delete(@target) @@ -186,7 +188,6 @@ module ActiveRecord end end - # Removes +records+ from this association calling +before_remove+ and # +after_remove+ callbacks. # @@ -195,22 +196,25 @@ 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) - records = flatten_deeper(records) - records.each { |record| raise_on_type_mismatch(record) } - - transaction do - records.each { |record| callback(:before_remove, record) } - - old_records = records.reject {|r| r.new_record? } + remove_records(records) do |records, old_records| delete_records(old_records) if old_records.any? - - records.each do |record| - @target.delete(record) - callback(:after_remove, record) - end + records.each { |record| @target.delete(record) } end end + # Destroy +records+ and remove them from this association calling + # +before_remove+ and +after_remove+ callbacks. + # + # Note that this method will _always_ remove records from the database + # ignoring the +:dependent+ option. + def destroy(*records) + remove_records(records) do |records, old_records| + old_records.each { |record| record.destroy } + end + + load_target + end + # Removes all records from this association. Returns +self+ so method calls may be chained. def clear return self if length.zero? # forces load_target if it hasn't happened already @@ -223,15 +227,16 @@ module ActiveRecord self end - - def destroy_all - transaction do - each { |record| record.destroy } - end + # Destory all the records from this association. + # + # See destroy for more info. + def destroy_all + load_target + destroy(@target) reset_target! end - + def create(attrs = {}) if attrs.is_a?(Array) attrs.collect { |attr| create(attr) } @@ -431,6 +436,18 @@ module ActiveRecord record end + def remove_records(*records) + records = flatten_deeper(records) + records.each { |record| raise_on_type_mismatch(record) } + + transaction do + records.each { |record| callback(:before_remove, record) } + old_records = records.reject { |r| r.new_record? } + yield(records, old_records) + records.each { |record| callback(:after_remove, record) } + end + end + def callback(method, record) callbacks_for(method).each do |callback| ActiveSupport::Callbacks::Callback.new(method, callback, record).call(@owner, record) diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb index d5d188ac..1c091e7d 100644 --- a/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -23,8 +23,8 @@ module ActiveRecord 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 less SELECT query if you use length. + # 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 SELECT query if you use #length. def size return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter? return @target.size if loaded? @@ -150,7 +150,7 @@ module ActiveRecord end else reflection_primary_key = @reflection.source_reflection.primary_key_name - source_primary_key = @reflection.klass.primary_key + source_primary_key = @reflection.through_reflection.klass.primary_key if @reflection.source_reflection.options[:as] polymorphic_join = "AND %s.%s = %s" % [ @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type", diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb index 96032300..b92cbbde 100644 --- a/vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb +++ b/vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb @@ -29,8 +29,17 @@ module ActiveRecord unless @target.nil? || @target == obj if dependent? && !dont_save - @target.destroy unless @target.new_record? - @owner.clear_association_cache + case @reflection.options[:dependent] + when :delete + @target.delete unless @target.new_record? + @owner.clear_association_cache + when :destroy + @target.destroy unless @target.new_record? + @owner.clear_association_cache + when :nullify + @target[@reflection.primary_key_name] = nil + @target.save unless @owner.new_record? || @target.new_record? + end else @target[@reflection.primary_key_name] = nil @target.save unless @owner.new_record? || @target.new_record? diff --git a/vendor/rails/activerecord/lib/active_record/autosave_association.rb b/vendor/rails/activerecord/lib/active_record/autosave_association.rb index 1c3d0567..741aa2ac 100644 --- a/vendor/rails/activerecord/lib/active_record/autosave_association.rb +++ b/vendor/rails/activerecord/lib/active_record/autosave_association.rb @@ -283,7 +283,7 @@ module ActiveRecord if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave) records.each do |record| if autosave && record.marked_for_destruction? - record.destroy + association.destroy(record) elsif @new_record_before_save || record.new_record? if autosave association.send(:insert_record, record, false, false) @@ -310,7 +310,7 @@ module ActiveRecord # This all happens inside a transaction, _if_ the Transactions module is included into # ActiveRecord::Base after the AutosaveAssociation module, which it does by default. def save_has_one_association(reflection) - if association = association_instance_get(reflection.name) + if (association = association_instance_get(reflection.name)) && !association.target.nil? if reflection.options[:autosave] && association.marked_for_destruction? association.destroy elsif new_record? || association.new_record? || association[reflection.primary_key_name] != id || reflection.options[:autosave] diff --git a/vendor/rails/activerecord/lib/active_record/base.rb b/vendor/rails/activerecord/lib/active_record/base.rb index 206b4efa..2a538511 100755 --- a/vendor/rails/activerecord/lib/active_record/base.rb +++ b/vendor/rails/activerecord/lib/active_record/base.rb @@ -416,7 +416,7 @@ module ActiveRecord #:nodoc: end @@subclasses = {} - + ## # :singleton-method: # Contains the database configuration - as is typically stored in config/database.yml - @@ -661,7 +661,6 @@ module ActiveRecord #:nodoc: connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) } end - # Returns true if a record exists in the table that matches the +id+ or # conditions given, or false otherwise. The argument can take five forms: # @@ -737,12 +736,12 @@ module ActiveRecord #:nodoc: # ==== Parameters # # * +id+ - This should be the id or an array of ids to be updated. - # * +attributes+ - This should be a Hash of attributes to be set on the object, or an array of Hashes. + # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes. # # ==== Examples # # # Updating one record: - # Person.update(15, { :user_name => 'Samuel', :group => 'expert' }) + # Person.update(15, :user_name => 'Samuel', :group => 'expert') # # # Updating multiple records: # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } @@ -1003,7 +1002,6 @@ module ActiveRecord #:nodoc: update_counters(id, counter_name => -1) end - # Attributes named in this macro are protected from mass-assignment, # such as new(attributes), # update_attributes(attributes), or @@ -1104,7 +1102,6 @@ module ActiveRecord #:nodoc: read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {}) 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 @@ -1347,7 +1344,7 @@ module ActiveRecord #:nodoc: subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information } end - def self_and_descendents_from_active_record#nodoc: + def self_and_descendants_from_active_record#nodoc: klass = self classes = [klass] while klass != klass.base_class @@ -1367,7 +1364,7 @@ module ActiveRecord #:nodoc: # module now. # Specify +options+ with additional translating options. def human_attribute_name(attribute_key_name, options = {}) - defaults = self_and_descendents_from_active_record.map do |klass| + defaults = self_and_descendants_from_active_record.map do |klass| :"#{klass.name.underscore}.#{attribute_key_name}" end defaults << options[:default] if options[:default] @@ -1382,7 +1379,7 @@ module ActiveRecord #:nodoc: # Default scope of the translation is activerecord.models # Specify +options+ with additional translating options. def human_name(options = {}) - defaults = self_and_descendents_from_active_record.map do |klass| + defaults = self_and_descendants_from_active_record.map do |klass| :"#{klass.name.underscore}" end defaults << self.name.humanize @@ -1417,7 +1414,6 @@ module ActiveRecord #:nodoc: end end - def quote_value(value, column = nil) #:nodoc: connection.quote(value,column) end @@ -1486,7 +1482,7 @@ module ActiveRecord #:nodoc: elsif match = DynamicScopeMatch.match(method_id) return true if all_attributes_exists?(match.attribute_names) end - + super end @@ -1537,7 +1533,7 @@ module ActiveRecord #:nodoc: end def reverse_sql_order(order_query) - reversed_query = order_query.split(/,/).each { |s| + reversed_query = order_query.to_s.split(/,/).each { |s| if s.match(/\s(asc|ASC)$/) s.gsub!(/\s(asc|ASC)$/, ' DESC') elsif s.match(/\s(desc|DESC)$/) @@ -1690,7 +1686,7 @@ module ActiveRecord #:nodoc: def construct_finder_sql(options) scope = scope(:find) sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} " - sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} " + sql << "FROM #{options[:from] || (scope && scope[:from]) || quoted_table_name} " add_joins!(sql, options[:joins], scope) add_conditions!(sql, options[:conditions], scope) @@ -1745,7 +1741,9 @@ module ActiveRecord #:nodoc: scoped_order = scope[:order] if scope if order sql << " ORDER BY #{order}" - sql << ", #{scoped_order}" if scoped_order + if scoped_order && scoped_order != order + sql << ", #{scoped_order}" + end else sql << " ORDER BY #{scoped_order}" if scoped_order end @@ -1754,12 +1752,12 @@ module ActiveRecord #:nodoc: def add_group!(sql, group, having, scope = :auto) if group sql << " GROUP BY #{group}" - sql << " HAVING #{having}" if having + sql << " HAVING #{sanitize_sql_for_conditions(having)}" if having else scope = scope(:find) if :auto == scope if scope && (scoped_group = scope[:group]) sql << " GROUP BY #{scoped_group}" - sql << " HAVING #{scope[:having]}" if scope[:having] + sql << " HAVING #{sanitize_sql_for_conditions(scope[:having])}" if scope[:having] end end end @@ -2014,7 +2012,6 @@ module ActiveRecord #:nodoc: end end - # 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 @@ -2111,7 +2108,7 @@ module ActiveRecord #:nodoc: end # Merge scopings - if action == :merge && current_scoped_methods + if [:merge, :reverse_merge].include?(action) && current_scoped_methods method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)| case hash[method] when Hash @@ -2133,7 +2130,11 @@ module ActiveRecord #:nodoc: end end else - hash[method] = hash[method].merge(params) + if action == :reverse_merge + hash[method] = hash[method].merge(params) + else + hash[method] = params.merge(hash[method]) + end end else hash[method] = params @@ -2143,7 +2144,6 @@ module ActiveRecord #:nodoc: end self.scoped_methods << method_scoping - begin yield ensure @@ -2174,7 +2174,7 @@ module ActiveRecord #:nodoc: # Test whether the given method and optional key are scoped. def scoped?(method, key = nil) #:nodoc: if current_scoped_methods && (scope = current_scoped_methods[method]) - !key || scope.has_key?(key) + !key || !scope[key].nil? end end @@ -2749,7 +2749,6 @@ module ActiveRecord #:nodoc: 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 self.attribute_names.inject({}) do |attrs, name| diff --git a/vendor/rails/activerecord/lib/active_record/batches.rb b/vendor/rails/activerecord/lib/active_record/batches.rb index 9e9c8fbb..5a6cecd4 100644 --- a/vendor/rails/activerecord/lib/active_record/batches.rb +++ b/vendor/rails/activerecord/lib/active_record/batches.rb @@ -4,21 +4,24 @@ module ActiveRecord base.extend(ClassMethods) end - # When processing large numbers of records, it's often a good idea to do so in batches to prevent memory ballooning. + # When processing large numbers of records, it's often a good idea to do + # so in batches to prevent memory ballooning. module ClassMethods - # Yields each record that was found by the find +options+. The find is performed by find_in_batches - # with a batch size of 1000 (or as specified by the +batch_size+ option). + # Yields each record that was found by the find +options+. The find is + # performed by find_in_batches with a batch size of 1000 (or as + # specified by the :batch_size option). # # Example: # - # Person.each(:conditions => "age > 21") do |person| + # Person.find_each(:conditions => "age > 21") do |person| # person.party_all_night! # end # - # Note: This method is only intended to use for batch processing of large amounts of records that wouldn't fit in - # memory all at once. If you just need to loop over less than 1000 records, it's probably better just to use the - # regular find methods. - def each(options = {}) + # Note: This method is only intended to use for batch processing of + # large amounts of records that wouldn't fit in memory all at once. If + # you just need to loop over less than 1000 records, it's probably + # better just to use the regular find methods. + def find_each(options = {}) find_in_batches(options) do |records| records.each { |record| yield record } end @@ -26,17 +29,22 @@ module ActiveRecord self end - # Yields each batch of records that was found by the find +options+ as an array. The size of each batch is - # set by the +batch_size+ option; the default is 1000. + # Yields each batch of records that was found by the find +options+ as + # an array. The size of each batch is set by the :batch_size + # option; the default is 1000. # - # You can control the starting point for the batch processing by supplying the +start+ option. This is especially - # useful if you want multiple workers dealing with the same processing queue. You can make worker 1 handle all the - # records between id 0 and 10,000 and worker 2 handle from 10,000 and beyond (by setting the +start+ option on that - # worker). + # You can control the starting point for the batch processing by + # supplying the :start option. This is especially useful if you + # want multiple workers dealing with the same processing queue. You can + # make worker 1 handle all the records between id 0 and 10,000 and + # worker 2 handle from 10,000 and beyond (by setting the :start + # option on that worker). # - # It's not possible to set the order. That is automatically set to ascending on the primary key ("id ASC") - # to make the batch ordering work. This also mean that this method only works with integer-based primary keys. - # You can't set the limit either, that's used to control the the batch sizes. + # It's not possible to set the order. That is automatically set to + # ascending on the primary key ("id ASC") to make the batch ordering + # work. This also mean that this method only works with integer-based + # primary keys. You can't set the limit either, that's used to control + # the the batch sizes. # # Example: # @@ -49,12 +57,15 @@ module ActiveRecord raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit] start = options.delete(:start).to_i + batch_size = options.delete(:batch_size) || 1000 - with_scope(:find => options.merge(:order => batch_order, :limit => options.delete(:batch_size) || 1000)) do + with_scope(:find => options.merge(:order => batch_order, :limit => batch_size)) do records = find(:all, :conditions => [ "#{table_name}.#{primary_key} >= ?", start ]) while records.any? yield records + + break if records.size < batch_size records = find(:all, :conditions => [ "#{table_name}.#{primary_key} > ?", records.last.id ]) end end diff --git a/vendor/rails/activerecord/lib/active_record/calculations.rb b/vendor/rails/activerecord/lib/active_record/calculations.rb index b239c032..f077818d 100644 --- a/vendor/rails/activerecord/lib/active_record/calculations.rb +++ b/vendor/rails/activerecord/lib/active_record/calculations.rb @@ -141,22 +141,30 @@ module ActiveRecord def construct_count_options_from_args(*args) options = {} column_name = :all - + # We need to handle # count() # count(:column_name=:all) # count(options={}) # count(column_name=:all, options={}) + # selects specified by scopes case args.size + when 0 + column_name = scope(:find)[:select] if scope(:find) when 1 - args[0].is_a?(Hash) ? options = args[0] : column_name = args[0] + if args[0].is_a?(Hash) + column_name = scope(:find)[:select] if scope(:find) + options = args[0] + else + column_name = args[0] + end when 2 column_name, options = args else raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}" - end if args.size > 0 - - [column_name, options] + end + + [column_name || :all, options] end def construct_calculation_sql(operation, column_name, options) #:nodoc: @@ -214,13 +222,15 @@ module ActiveRecord end if options[:group] && options[:having] + having = sanitize_sql_for_conditions(options[:having]) + # FrontBase requires identifiers in the HAVING clause and chokes on function calls if connection.adapter_name == 'FrontBase' - options[:having].downcase! - options[:having].gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias) + having.downcase! + having.gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias) end - sql << " HAVING #{options[:having]} " + sql << " HAVING #{having} " end sql << " ORDER BY #{options[:order]} " if options[:order] diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index cc9c4650..75420f69 100644 --- a/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -18,7 +18,7 @@ module ActiveRecord db.busy_timeout(config[:timeout]) unless config[:timeout].nil? - ConnectionAdapters::SQLite3Adapter.new(db, logger) + ConnectionAdapters::SQLite3Adapter.new(db, logger, config) end end diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 5390f49f..afd6472d 100644 --- a/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -17,9 +17,9 @@ module ActiveRecord # "Downgrade" deprecated sqlite API if SQLite.const_defined?(:Version) - ConnectionAdapters::SQLite2Adapter.new(db, logger) + ConnectionAdapters::SQLite2Adapter.new(db, logger, config) else - ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger) + ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger, config) end end end @@ -72,10 +72,31 @@ module ActiveRecord # # * :database - Path to the database file. class SQLiteAdapter < AbstractAdapter + class Version + include Comparable + + def initialize(version_string) + @version = version_string.split('.').map(&:to_i) + end + + def <=>(version_string) + @version <=> version_string.split('.').map(&:to_i) + end + end + + def initialize(connection, logger, config) + super(connection, logger) + @config = config + end + def adapter_name #:nodoc: 'SQLite' end + def supports_ddl_transactions? + sqlite_version >= '2.0.0' + end + def supports_migrations? #:nodoc: true end @@ -83,6 +104,10 @@ module ActiveRecord def requires_reloading? true end + + def supports_add_column? + sqlite_version >= '3.1.6' + end def disconnect! super @@ -164,7 +189,6 @@ module ActiveRecord catch_schema_changes { @connection.rollback } end - # SELECT ... FOR UPDATE is redundant since the table is locked. def add_lock!(sql, options) #:nodoc: sql @@ -213,14 +237,20 @@ module ActiveRecord execute "ALTER TABLE #{name} RENAME TO #{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 @connection.respond_to?(:transaction_active?) && @connection.transaction_active? - raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction' + 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 - - super(table_name, column_name, type, options) - # See last paragraph on http://www.sqlite.org/lang_altertable.html - execute "VACUUM" end def remove_column(table_name, *column_names) #:nodoc: @@ -380,7 +410,7 @@ module ActiveRecord end def sqlite_version - @sqlite_version ||= select_value('select sqlite_version(*)') + @sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)')) end def default_primary_key_type @@ -393,23 +423,9 @@ module ActiveRecord end class SQLite2Adapter < SQLiteAdapter # :nodoc: - def supports_count_distinct? #:nodoc: - false - end - def rename_table(name, new_name) move_table(name, new_name) end - - def add_column(table_name, column_name, type, options = {}) #:nodoc: - if @connection.respond_to?(:transaction_active?) && @connection.transaction_active? - raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction' - end - - alter_table(table_name) do |definition| - definition.column(column_name, type, options) - end - end end class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc: diff --git a/vendor/rails/activerecord/lib/active_record/locking/optimistic.rb b/vendor/rails/activerecord/lib/active_record/locking/optimistic.rb index ff9899d0..7fa7e267 100644 --- a/vendor/rails/activerecord/lib/active_record/locking/optimistic.rb +++ b/vendor/rails/activerecord/lib/active_record/locking/optimistic.rb @@ -23,6 +23,16 @@ module ActiveRecord # p2.first_name = "should fail" # p2.save # Raises a ActiveRecord::StaleObjectError # + # Optimistic locking will also check for stale data when objects are destroyed. Example: + # + # p1 = Person.find(1) + # p2 = Person.find(1) + # + # p1.first_name = "Michael" + # p1.save + # + # p2.destroy # 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. # @@ -39,6 +49,7 @@ module ActiveRecord base.lock_optimistically = true base.alias_method_chain :update, :lock + base.alias_method_chain :destroy, :lock base.alias_method_chain :attributes_from_column_definition, :lock class << base @@ -98,6 +109,28 @@ module ActiveRecord end end + def destroy_with_lock #:nodoc: + return destroy_without_lock unless locking_enabled? + + unless new_record? + lock_col = self.class.locking_column + previous_value = send(lock_col).to_i + + affected_rows = connection.delete( + "DELETE FROM #{self.class.quoted_table_name} " + + "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " + + "AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}", + "#{self.class.name} Destroy" + ) + + unless affected_rows == 1 + raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object" + end + end + + freeze + end + module ClassMethods DEFAULT_LOCKING_COLUMN = 'lock_version' diff --git a/vendor/rails/activerecord/lib/active_record/named_scope.rb b/vendor/rails/activerecord/lib/active_record/named_scope.rb index 8a7449f9..1f3ef300 100644 --- a/vendor/rails/activerecord/lib/active_record/named_scope.rb +++ b/vendor/rails/activerecord/lib/active_record/named_scope.rb @@ -89,7 +89,12 @@ module ActiveRecord when Hash options when Proc - options.call(*args) + case parent_scope + when Scope + with_scope(:find => parent_scope.proxy_options) { options.call(*args) } + else + options.call(*args) + end end, &block) end (class << self; self end).instance_eval do @@ -99,7 +104,7 @@ module ActiveRecord end end end - + class Scope attr_reader :proxy_scope, :proxy_options, :current_scoped_methods_when_defined NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? respond_to?).to_set @@ -112,6 +117,7 @@ module ActiveRecord delegate :scopes, :with_scope, :to => :proxy_scope def initialize(proxy_scope, options, &block) + options ||= {} [options[:extend]].flatten.each { |extension| extend extension } if options[:extend] extend Module.new(&block) if block_given? unless Scope === proxy_scope @@ -170,7 +176,7 @@ module ActiveRecord if scopes.include?(method) scopes[method].call(self, *args) else - with_scope :find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {} do + with_scope({:find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {}}, :reverse_merge) do method = :new if method == :build if current_scoped_methods_when_defined with_scope current_scoped_methods_when_defined do diff --git a/vendor/rails/activerecord/lib/active_record/reflection.rb b/vendor/rails/activerecord/lib/active_record/reflection.rb index e69bfb13..2d4c1d55 100644 --- a/vendor/rails/activerecord/lib/active_record/reflection.rb +++ b/vendor/rails/activerecord/lib/active_record/reflection.rb @@ -197,7 +197,7 @@ module ActiveRecord def counter_cache_column if options[:counter_cache] == true - "#{active_record.name.underscore.pluralize}_count" + "#{active_record.name.demodulize.underscore.pluralize}_count" elsif options[:counter_cache] options[:counter_cache] end diff --git a/vendor/rails/activerecord/lib/active_record/serializers/xml_serializer.rb b/vendor/rails/activerecord/lib/active_record/serializers/xml_serializer.rb index 4749823b..fa758746 100644 --- a/vendor/rails/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/vendor/rails/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -231,16 +231,22 @@ module ActiveRecord #:nodoc: def add_associations(association, records, opts) if records.is_a?(Enumerable) tag = reformat_name(association.to_s) + type = options[:skip_types] ? {} : {:type => "array"} + if records.empty? - builder.tag!(tag, :type => :array) + builder.tag!(tag, type) else - builder.tag!(tag, :type => :array) do + builder.tag!(tag, type) do association_name = association.to_s.singularize records.each do |record| - record.to_xml opts.merge( - :root => association_name, - :type => (record.class.to_s.underscore == association_name ? nil : record.class.name) - ) + if options[:skip_types] + record_type = {} + else + record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name + record_type = {:type => record_class} + end + + record.to_xml opts.merge(:root => association_name).merge(record_type) end end end diff --git a/vendor/rails/activerecord/lib/active_record/test_case.rb b/vendor/rails/activerecord/lib/active_record/test_case.rb index 211dd788..8c6abaac 100644 --- a/vendor/rails/activerecord/lib/active_record/test_case.rb +++ b/vendor/rails/activerecord/lib/active_record/test_case.rb @@ -49,5 +49,18 @@ module ActiveRecord ActiveRecord::Base.clear_all_connections! ActiveRecord::Base.establish_connection(@connection) 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 end end diff --git a/vendor/rails/activerecord/lib/active_record/validations.rb b/vendor/rails/activerecord/lib/active_record/validations.rb index 8f3c8056..d2d12b80 100644 --- a/vendor/rails/activerecord/lib/active_record/validations.rb +++ b/vendor/rails/activerecord/lib/active_record/validations.rb @@ -89,7 +89,7 @@ module ActiveRecord message, options[:default] = options[:default], message if options[:default].is_a?(Symbol) - defaults = @base.class.self_and_descendents_from_active_record.map do |klass| + defaults = @base.class.self_and_descendants_from_active_record.map do |klass| [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}", :"models.#{klass.name.underscore}.#{message}" ] end @@ -720,20 +720,20 @@ module ActiveRecord # class (which has a database table to query from). finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? } - is_text_column = finder_class.columns_hash[attr_name.to_s].text? + column = finder_class.columns_hash[attr_name.to_s] if value.nil? comparison_operator = "IS ?" - elsif is_text_column + elsif column.text? comparison_operator = "#{connection.case_sensitive_equality_operator} ?" - value = value.to_s + value = column.limit ? value.to_s[0, column.limit] : value.to_s else comparison_operator = "= ?" end sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}" - if value.nil? || (configuration[:case_sensitive] || !is_text_column) + if value.nil? || (configuration[:case_sensitive] || !column.text?) condition_sql = "#{sql_attribute} #{comparison_operator}" condition_params = [value] else @@ -802,7 +802,7 @@ module ActiveRecord # 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 ), :message => "woah! what are you then!??!!" + # validates_inclusion_of :gender, :in => %w( m f ) # validates_inclusion_of :age, :in => 0..99 # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list" # end @@ -1040,6 +1040,11 @@ module ActiveRecord errors.empty? end + # Performs the opposite of valid?. Returns true if errors were added, false otherwise. + def invalid? + !valid? + end + # Returns the Errors object that holds all information about attribute error messages. def errors @errors ||= Errors.new(self) diff --git a/vendor/rails/activerecord/lib/active_record/version.rb b/vendor/rails/activerecord/lib/active_record/version.rb index 809e9189..852807b4 100644 --- a/vendor/rails/activerecord/lib/active_record/version.rb +++ b/vendor/rails/activerecord/lib/active_record/version.rb @@ -2,7 +2,7 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/activerecord/test/cases/associations/belongs_to_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/belongs_to_associations_test.rb index 92ed465a..13a78a18 100644 --- a/vendor/rails/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/vendor/rails/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -154,6 +154,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 0, Topic.find(t2.id).replies.size end + def test_belongs_to_reassign_with_namespaced_models_and_counters + t1 = Web::Topic.create("title" => "t1") + t2 = Web::Topic.create("title" => "t2") + r1 = Web::Reply.new("title" => "r1", "content" => "r1") + r1.topic = t1 + + assert r1.save + assert_equal 1, Web::Topic.find(t1.id).replies.size + assert_equal 0, Web::Topic.find(t2.id).replies.size + + r1.topic = Web::Topic.find(t2.id) + + assert r1.save + assert_equal 0, Web::Topic.find(t1.id).replies.size + assert_equal 1, Web::Topic.find(t2.id).replies.size + end + def test_belongs_to_counter_after_save topic = Topic.create!(:title => "monday night") topic.replies.create!(:title => "re: monday night", :content => "football") @@ -301,12 +318,28 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_belongs_to_proxy_should_not_respond_to_private_methods - assert_raises(NoMethodError) { companies(:first_firm).private_method } - assert_raises(NoMethodError) { companies(:second_client).firm.private_method } + assert_raise(NoMethodError) { companies(:first_firm).private_method } + assert_raise(NoMethodError) { companies(:second_client).firm.private_method } end def test_belongs_to_proxy_should_respond_to_private_methods_via_send companies(:first_firm).send(:private_method) companies(:second_client).firm.send(:private_method) end + + def test_save_of_record_with_loaded_belongs_to + @account = companies(:first_firm).account + + assert_nothing_raised do + Account.find(@account.id).save! + Account.find(@account.id, :include => :firm).save! + end + + @account.firm.delete + + assert_nothing_raised do + Account.find(@account.id).save! + Account.find(@account.id, :include => :firm).save! + end + end end diff --git a/vendor/rails/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/vendor/rails/activerecord/test/cases/associations/eager_load_nested_include_test.rb index 12dec5cc..1b2e0fc1 100644 --- a/vendor/rails/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/vendor/rails/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -1,4 +1,9 @@ require 'cases/helper' +require 'models/author' +require 'models/post' +require 'models/comment' +require 'models/category' +require 'models/categorization' module Remembered def self.included(base) @@ -99,3 +104,27 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase end end end + +class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase + def setup + @davey_mcdave = Author.create(:name => 'Davey McDave') + @first_post = @davey_mcdave.posts.create(:title => 'Davey Speaks', :body => 'Expressive wordage') + @first_comment = @first_post.comments.create(:body => 'Inflamatory doublespeak') + @first_categorization = @davey_mcdave.categorizations.create(:category => Category.first, :post => @first_post) + end + + def teardown + @davey_mcdave.destroy + @first_post.destroy + @first_comment.destroy + @first_categorization.destroy + end + + def test_missing_data_in_a_nested_include_should_not_cause_errors_when_constructing_objects + 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' + end + end +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/cases/associations/eager_test.rb b/vendor/rails/activerecord/test/cases/associations/eager_test.rb index 14099d41..40723814 100644 --- a/vendor/rails/activerecord/test/cases/associations/eager_test.rb +++ b/vendor/rails/activerecord/test/cases/associations/eager_test.rb @@ -549,16 +549,16 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_invalid_association_reference - assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { + assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { post = Post.find(6, :include=> :monkeys ) } - assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { + assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { post = Post.find(6, :include=>[ :monkeys ]) } - assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { + assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { post = Post.find(6, :include=>[ 'monkeys' ]) } - assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") { + assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") { post = Post.find(6, :include=>[ :monkeys, :elephants ]) } end diff --git a/vendor/rails/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 0f43d97f..5e8b2cad 100644 --- a/vendor/rails/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/vendor/rails/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -381,6 +381,33 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_date_from_db Date.new(2004, 10, 10), Developer.find(1).projects.first.joined_on.to_date end + def test_destroying + david = Developer.find(1) + active_record = Project.find(1) + david.projects.reload + assert_equal 2, david.projects.size + assert_equal 3, active_record.developers.size + + assert_difference "Project.count", -1 do + david.projects.destroy(active_record) + end + + assert_equal 1, david.reload.projects.size + assert_equal 1, david.projects(true).size + end + + def test_destroying_array + david = Developer.find(1) + david.projects.reload + + assert_difference "Project.count", -Project.count do + david.projects.destroy(Project.find(:all)) + end + + assert_equal 0, david.reload.projects.size + assert_equal 0, david.projects(true).size + end + def test_destroy_all david = Developer.find(1) david.projects.reload @@ -616,7 +643,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_updating_attributes_on_rich_associations david = projects(:action_controller).developers.first david.name = "DHH" - assert_raises(ActiveRecord::ReadOnlyRecord) { david.save! } + assert_raise(ActiveRecord::ReadOnlyRecord) { david.save! } end def test_updating_attributes_on_rich_associations_with_limited_find_from_reflection @@ -740,6 +767,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal developer, project.developers.find(:first) assert_equal project, developer.projects.find(:first) end + + def test_self_referential_habtm_without_foreign_key_set_should_raise_exception + assert_raise(ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded) { + Member.class_eval do + has_and_belongs_to_many :friends, :class_name => "Member", :join_table => "member_friends" + end + } + end def test_dynamic_find_should_respect_association_include # SQL error in sort clause if :include is not included diff --git a/vendor/rails/activerecord/test/cases/associations/has_many_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_many_associations_test.rb index 5efbc5bd..30edf79a 100644 --- a/vendor/rails/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/vendor/rails/activerecord/test/cases/associations/has_many_associations_test.rb @@ -70,6 +70,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 2, companies(:first_firm).limited_clients.find(:all, :limit => nil).size 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.find_by_type('Client') @@ -176,7 +180,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_ids firm = Firm.find(:first) - assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find } + assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find } client = firm.clients.find(2) assert_kind_of Client, client @@ -190,7 +194,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 2, client_ary.size assert_equal client, client_ary.first - assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) } + assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) } end def test_find_string_ids_when_using_finder_sql @@ -215,6 +219,45 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length end + def test_find_each + firm = companies(:first_firm) + + assert ! firm.clients.loaded? + + assert_queries(3) do + firm.clients.find_each(:batch_size => 1) {|c| assert_equal firm.id, c.firm_id } + end + + assert ! firm.clients.loaded? + end + + def test_find_each_with_conditions + firm = companies(:first_firm) + + assert_queries(2) do + firm.clients.find_each(:batch_size => 1, :conditions => {:name => "Microsoft"}) do |c| + assert_equal firm.id, c.firm_id + assert_equal "Microsoft", c.name + end + end + + assert ! firm.clients.loaded? + end + + def test_find_in_batches + firm = companies(:first_firm) + + assert ! firm.clients.loaded? + + assert_queries(2) do + firm.clients.find_in_batches(:batch_size => 2) do |clients| + clients.each {|c| assert_equal firm.id, c.firm_id } + end + end + + assert ! firm.clients.loaded? + end + def test_find_all_sanitized firm = Firm.find(:first) summit = firm.clients.find(:all, :conditions => "name = 'Summit'") @@ -238,7 +281,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_in_collection assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name - assert_raises(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) } + assert_raise(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) } end def test_find_grouped @@ -278,36 +321,36 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_create_with_bang_on_has_many_when_parent_is_new_raises - assert_raises(ActiveRecord::RecordNotSaved) do + assert_raise(ActiveRecord::RecordNotSaved) do firm = Firm.new firm.plain_clients.create! :name=>"Whoever" end end def test_regular_create_on_has_many_when_parent_is_new_raises - assert_raises(ActiveRecord::RecordNotSaved) do + assert_raise(ActiveRecord::RecordNotSaved) do firm = Firm.new firm.plain_clients.create :name=>"Whoever" end end def test_create_with_bang_on_has_many_raises_when_record_not_saved - assert_raises(ActiveRecord::RecordInvalid) do + assert_raise(ActiveRecord::RecordInvalid) do firm = Firm.find(:first) firm.plain_clients.create! end end def test_create_with_bang_on_habtm_when_parent_is_new_raises - assert_raises(ActiveRecord::RecordNotSaved) do + assert_raise(ActiveRecord::RecordNotSaved) do Developer.new("name" => "Aredridel").projects.create! end end def test_adding_a_mismatch_class - assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << nil } - assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << 1 } - assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << Topic.find(1) } + assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << nil } + assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << 1 } + assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << Topic.find(1) } end def test_adding_a_collection @@ -602,7 +645,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_invalid_belongs_to_dependent_option_raises_exception - assert_raises ArgumentError do + assert_raise ArgumentError do Author.belongs_to :special_author_address, :dependent => :nullify end end @@ -628,13 +671,37 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_deleting_type_mismatch david = Developer.find(1) david.projects.reload - assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(1) } + assert_raise(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(1) } end def test_deleting_self_type_mismatch david = Developer.find(1) david.projects.reload - assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(Project.find(1).developers) } + assert_raise(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(Project.find(1).developers) } + end + + def test_destroying + force_signal37_to_load_all_clients_of_firm + + assert_difference "Client.count", -1 do + companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first) + end + + assert_equal 0, companies(:first_firm).reload.clients_of_firm.size + assert_equal 0, companies(:first_firm).clients_of_firm(true).size + end + + def test_destroying_a_collection + force_signal37_to_load_all_clients_of_firm + companies(:first_firm).clients_of_firm.create("name" => "Another Client") + assert_equal 2, companies(:first_firm).clients_of_firm.size + + assert_difference "Client.count", -2 do + companies(:first_firm).clients_of_firm.destroy([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]]) + end + + assert_equal 0, companies(:first_firm).reload.clients_of_firm.size + assert_equal 0, companies(:first_firm).clients_of_firm(true).size end def test_destroy_all diff --git a/vendor/rails/activerecord/test/cases/associations/has_many_through_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_many_through_associations_test.rb index 1e5d1a02..97efca78 100644 --- a/vendor/rails/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/vendor/rails/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1,14 +1,19 @@ require "cases/helper" require 'models/post' require 'models/person' +require 'models/reference' +require 'models/job' require 'models/reader' require 'models/comment' require 'models/tag' require 'models/tagging' require 'models/author' +require 'models/owner' +require 'models/pet' +require 'models/toy' class HasManyThroughAssociationsTest < ActiveRecord::TestCase - fixtures :posts, :readers, :people, :comments, :authors + fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys def test_associate_existing assert_queries(2) { posts(:thinking);people(:david) } @@ -87,6 +92,24 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert posts(:welcome).reload.people(true).empty? end + def test_destroy_association + assert_difference "Person.count", -1 do + posts(:welcome).people.destroy(people(:michael)) + end + + assert posts(:welcome).reload.people.empty? + assert posts(:welcome).people(true).empty? + end + + def test_destroy_all + assert_difference "Person.count", -1 do + posts(:welcome).people.destroy_all + end + + assert posts(:welcome).reload.people.empty? + assert posts(:welcome).people(true).empty? + end + def test_replace_association assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)} @@ -249,4 +272,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase author.author_favorites.create(:favorite_author_id => 3) assert_equal post.author.author_favorites, post.author_favorites end + + def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys + assert_equal 1, owners(:blackbeard).toys.count + end end diff --git a/vendor/rails/activerecord/test/cases/associations/has_one_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_one_associations_test.rb index e81bba79..1ddb3f49 100644 --- a/vendor/rails/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/vendor/rails/activerecord/test/cases/associations/has_one_associations_test.rb @@ -59,8 +59,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_type_mismatch - assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 } - assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) } + assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 } + assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) } end def test_natural_assignment @@ -76,7 +76,25 @@ class HasOneAssociationsTest < ActiveRecord::TestCase companies(:first_firm).save assert_nil companies(:first_firm).account # account is dependent, therefore is destroyed when reference to owner is lost - assert_raises(ActiveRecord::RecordNotFound) { Account.find(old_account_id) } + assert_raise(ActiveRecord::RecordNotFound) { Account.find(old_account_id) } + end + + def test_nullification_on_association_change + firm = companies(:rails_core) + old_account_id = firm.account.id + firm.account = Account.new + # account is dependent with nullify, therefore its firm_id should be nil + assert_nil Account.find(old_account_id).firm_id + end + + def test_association_changecalls_delete + companies(:first_firm).deletable_account = Account.new + assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id] + end + + def test_association_change_calls_destroy + companies(:first_firm).account = Account.new + assert_equal [companies(:first_firm).id], Account.destroyed_account_ids[companies(:first_firm).id] end def test_natural_assignment_to_already_associated_record @@ -263,8 +281,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_has_one_proxy_should_not_respond_to_private_methods - assert_raises(NoMethodError) { accounts(:signals37).private_method } - assert_raises(NoMethodError) { companies(:first_firm).account.private_method } + assert_raise(NoMethodError) { accounts(:signals37).private_method } + assert_raise(NoMethodError) { companies(:first_firm).account.private_method } end def test_has_one_proxy_should_respond_to_private_methods_via_send @@ -272,4 +290,20 @@ class HasOneAssociationsTest < ActiveRecord::TestCase companies(:first_firm).account.send(:private_method) end + def test_save_of_record_with_loaded_has_one + @firm = companies(:first_firm) + assert_not_nil @firm.account + + assert_nothing_raised do + Firm.find(@firm.id).save! + Firm.find(@firm.id, :include => :account).save! + end + + @firm.account.destroy + + assert_nothing_raised do + Firm.find(@firm.id).save! + Firm.find(@firm.id, :include => :account).save! + end + end end diff --git a/vendor/rails/activerecord/test/cases/associations/has_one_through_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_one_through_associations_test.rb index f65d76e2..12c59875 100644 --- a/vendor/rails/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/vendor/rails/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -115,8 +115,8 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_has_one_through_proxy_should_not_respond_to_private_methods - assert_raises(NoMethodError) { clubs(:moustache_club).private_method } - assert_raises(NoMethodError) { @member.club.private_method } + assert_raise(NoMethodError) { clubs(:moustache_club).private_method } + assert_raise(NoMethodError) { @member.club.private_method } end def test_has_one_through_proxy_should_respond_to_private_methods_via_send @@ -173,4 +173,20 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase assert_not_nil assert_no_queries { @new_detail.member_type } end + def test_save_of_record_with_loaded_has_one_through + @club = @member.club + assert_not_nil @club.sponsored_member + + assert_nothing_raised do + Club.find(@club.id).save! + Club.find(@club.id, :include => :sponsored_member).save! + end + + @club.sponsor.destroy + + assert_nothing_raised do + Club.find(@club.id).save! + Club.find(@club.id, :include => :sponsored_member).save! + end + end end diff --git a/vendor/rails/activerecord/test/cases/attribute_methods_test.rb b/vendor/rails/activerecord/test/cases/attribute_methods_test.rb index 759f9f38..17ed3024 100644 --- a/vendor/rails/activerecord/test/cases/attribute_methods_test.rb +++ b/vendor/rails/activerecord/test/cases/attribute_methods_test.rb @@ -100,7 +100,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase %w(save create_or_update).each do |method| klass = Class.new ActiveRecord::Base klass.class_eval "def #{method}() 'defined #{method}' end" - assert_raises ActiveRecord::DangerousAttributeError do + assert_raise ActiveRecord::DangerousAttributeError do klass.instance_method_already_implemented?(method) end end diff --git a/vendor/rails/activerecord/test/cases/autosave_association_test.rb b/vendor/rails/activerecord/test/cases/autosave_association_test.rb index b179bd82..436f50d3 100644 --- a/vendor/rails/activerecord/test/cases/autosave_association_test.rb +++ b/vendor/rails/activerecord/test/cases/autosave_association_test.rb @@ -556,6 +556,41 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase assert_raise(RuntimeError) { assert !@pirate.save } assert_equal before, @pirate.reload.send(association_name) 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_#{association_name}") do + association_name_with_callbacks = "#{association_name}_with_#{callback_type}_callbacks" + + pirate = Pirate.new(:catchphrase => "Arr") + pirate.send(association_name_with_callbacks).build(:name => "Crowe the One-Eyed") + + expected = [ + "before_adding_#{callback_type}_#{association_name.singularize}_", + "after_adding_#{callback_type}_#{association_name.singularize}_" + ] + + assert_equal expected, pirate.ship_log + end + + define_method("test_should_run_remove_callback_#{callback_type}s_for_#{association_name}") do + association_name_with_callbacks = "#{association_name}_with_#{callback_type}_callbacks" + + @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed") + @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction } + child_id = @pirate.send(association_name_with_callbacks).first.id + + @pirate.ship_log.clear + @pirate.save + + expected = [ + "before_removing_#{callback_type}_#{association_name.singularize}_#{child_id}", + "after_removing_#{callback_type}_#{association_name.singularize}_#{child_id}" + ] + + assert_equal expected, @pirate.ship_log + end + end end end diff --git a/vendor/rails/activerecord/test/cases/base_test.rb b/vendor/rails/activerecord/test/cases/base_test.rb index eec16c04..99d77961 100755 --- a/vendor/rails/activerecord/test/cases/base_test.rb +++ b/vendor/rails/activerecord/test/cases/base_test.rb @@ -424,8 +424,8 @@ class BasicsTest < ActiveRecord::TestCase def test_non_attribute_access_and_assignment topic = Topic.new assert !topic.respond_to?("mumbo") - assert_raises(NoMethodError) { topic.mumbo } - assert_raises(NoMethodError) { topic.mumbo = 5 } + assert_raise(NoMethodError) { topic.mumbo } + assert_raise(NoMethodError) { topic.mumbo = 5 } end def test_preserving_date_objects @@ -490,7 +490,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_record_not_found_exception - assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) } + assert_raise(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) } end def test_initialize_with_attributes @@ -848,7 +848,7 @@ class BasicsTest < ActiveRecord::TestCase client.delete assert client.frozen? assert_kind_of Firm, client.firm - assert_raises(ActiveSupport::FrozenObjectError) { client.name = "something else" } + assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" } end def test_destroy_new_record @@ -862,7 +862,7 @@ class BasicsTest < ActiveRecord::TestCase client.destroy assert client.frozen? assert_kind_of Firm, client.firm - assert_raises(ActiveSupport::FrozenObjectError) { client.name = "something else" } + assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" } end def test_update_attribute @@ -910,8 +910,8 @@ class BasicsTest < ActiveRecord::TestCase def test_mass_assignment_should_raise_exception_if_accessible_and_protected_attribute_writers_are_both_used topic = TopicWithProtectedContentAndAccessibleAuthorName.new - assert_raises(RuntimeError) { topic.attributes = { "author_name" => "me" } } - assert_raises(RuntimeError) { topic.attributes = { "content" => "stuff" } } + assert_raise(RuntimeError) { topic.attributes = { "author_name" => "me" } } + assert_raise(RuntimeError) { topic.attributes = { "content" => "stuff" } } end def test_mass_assignment_protection @@ -949,7 +949,7 @@ class BasicsTest < ActiveRecord::TestCase def test_mass_assigning_invalid_attribute firm = Firm.new - assert_raises(ActiveRecord::UnknownAttributeError) do + assert_raise(ActiveRecord::UnknownAttributeError) do firm.attributes = { "id" => 5, "type" => "Client", "i_dont_even_exist" => 20 } end end @@ -1402,7 +1402,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_sql_injection_via_find - assert_raises(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do + assert_raise(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do Topic.find("123456 OR id > 0") end end @@ -1790,6 +1790,11 @@ class BasicsTest < ActiveRecord::TestCase assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last 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.with_scope(:find => { :order => 'developers.salary ASC' }) do Developer.find(:last) @@ -2099,18 +2104,4 @@ class BasicsTest < ActiveRecord::TestCase assert_equal custom_datetime, parrot[attribute] end end - - private - 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 diff --git a/vendor/rails/activerecord/test/cases/batches_test.rb b/vendor/rails/activerecord/test/cases/batches_test.rb index 108d6791..5009a908 100644 --- a/vendor/rails/activerecord/test/cases/batches_test.rb +++ b/vendor/rails/activerecord/test/cases/batches_test.rb @@ -11,7 +11,7 @@ class EachTest < ActiveRecord::TestCase def test_each_should_excecute_one_query_per_batch assert_queries(Post.count + 1) do - Post.each(:batch_size => 1) do |post| + Post.find_each(:batch_size => 1) do |post| assert_kind_of Post, post end end @@ -19,13 +19,13 @@ class EachTest < ActiveRecord::TestCase def test_each_should_raise_if_the_order_is_set assert_raise(RuntimeError) do - Post.each(:order => "title") { |post| post } + Post.find_each(:order => "title") { |post| post } end end def test_each_should_raise_if_the_limit_is_set assert_raise(RuntimeError) do - Post.each(:limit => 1) { |post| post } + Post.find_each(:limit => 1) { |post| post } end end @@ -46,4 +46,16 @@ class EachTest < ActiveRecord::TestCase end end end + + def test_find_in_batches_shouldnt_excute_query_unless_needed + post_count = Post.count + + assert_queries(2) do + Post.find_in_batches(:batch_size => post_count) {|batch| assert_kind_of Array, batch } + end + + assert_queries(1) do + Post.find_in_batches(:batch_size => post_count + 1) {|batch| assert_kind_of Array, batch } + end + end end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/cases/calculations_test.rb b/vendor/rails/activerecord/test/cases/calculations_test.rb index 5a4ed429..56dcdea1 100644 --- a/vendor/rails/activerecord/test/cases/calculations_test.rb +++ b/vendor/rails/activerecord/test/cases/calculations_test.rb @@ -92,6 +92,14 @@ class CalculationsTest < ActiveRecord::TestCase 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]) + assert_nil c[1] + assert_equal 105, c[6] + assert_equal 60, c[2] + end + def test_should_group_by_summed_association c = Account.sum(:credit_limit, :group => :firm) assert_equal 50, c[companies(:first_firm)] @@ -247,8 +255,8 @@ class CalculationsTest < ActiveRecord::TestCase Company.send(:validate_calculation_options, :count, :include => true) end - assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum, :foo => :bar) } - assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) } + assert_raise(ArgumentError) { Company.send(:validate_calculation_options, :sum, :foo => :bar) } + assert_raise(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) } end def test_should_count_selected_field_with_include @@ -256,6 +264,19 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 4, Account.count(:distinct => true, :include => :firm, :select => :credit_limit) end + def test_should_count_scoped_select + Account.update_all("credit_limit = NULL") + assert_equal 0, Account.scoped(:select => "credit_limit").count + end + + def test_should_count_scoped_select_with_options + Account.update_all("credit_limit = NULL") + Account.last.update_attribute('credit_limit', 49) + Account.first.update_attribute('credit_limit', 51) + + assert_equal 1, Account.scoped(:select => "credit_limit").count(:conditions => ['credit_limit >= 50']) + end + def test_should_count_manual_select_with_include assert_equal 6, Account.count(:select => "DISTINCT accounts.id", :include => :firm) end diff --git a/vendor/rails/activerecord/test/cases/callbacks_test.rb b/vendor/rails/activerecord/test/cases/callbacks_test.rb index 33b1ea03..95fddaee 100644 --- a/vendor/rails/activerecord/test/cases/callbacks_test.rb +++ b/vendor/rails/activerecord/test/cases/callbacks_test.rb @@ -352,13 +352,13 @@ class CallbacksTest < ActiveRecord::TestCase david = ImmutableDeveloper.find(1) assert david.valid? assert !david.save - assert_raises(ActiveRecord::RecordNotSaved) { david.save! } + assert_raise(ActiveRecord::RecordNotSaved) { david.save! } david = ImmutableDeveloper.find(1) david.salary = 10_000_000 assert !david.valid? assert !david.save - assert_raises(ActiveRecord::RecordInvalid) { david.save! } + assert_raise(ActiveRecord::RecordInvalid) { david.save! } someone = CallbackCancellationDeveloper.find(1) someone.cancel_before_save = true diff --git a/vendor/rails/activerecord/test/cases/dirty_test.rb b/vendor/rails/activerecord/test/cases/dirty_test.rb index 5f5707b3..ac95bac4 100644 --- a/vendor/rails/activerecord/test/cases/dirty_test.rb +++ b/vendor/rails/activerecord/test/cases/dirty_test.rb @@ -228,7 +228,7 @@ class DirtyTest < ActiveRecord::TestCase pirate = Pirate.new pirate.parrot_id = 1 - assert_raises(ActiveRecord::RecordInvalid) { pirate.save! } + assert_raise(ActiveRecord::RecordInvalid) { pirate.save! } check_pirate_after_save_failure(pirate) end diff --git a/vendor/rails/activerecord/test/cases/finder_test.rb b/vendor/rails/activerecord/test/cases/finder_test.rb index ee8f4901..d8778957 100644 --- a/vendor/rails/activerecord/test/cases/finder_test.rb +++ b/vendor/rails/activerecord/test/cases/finder_test.rb @@ -146,7 +146,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_ids_missing_one - assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) } end def test_find_all_with_limit @@ -191,6 +191,13 @@ class FinderTest < ActiveRecord::TestCase 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 } + end + def test_find_with_entire_select_statement topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'" @@ -229,7 +236,7 @@ class FinderTest < ActiveRecord::TestCase end def test_unexisting_record_exception_handling - assert_raises(ActiveRecord::RecordNotFound) { + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1).parent } @@ -238,7 +245,7 @@ class FinderTest < ActiveRecord::TestCase def test_find_only_some_columns topic = Topic.find(1, :select => "author_name") - assert_raises(ActiveRecord::MissingAttributeError) {topic.title} + assert_raise(ActiveRecord::MissingAttributeError) {topic.title} assert_equal "David", topic.author_name assert !topic.attribute_present?("title") #assert !topic.respond_to?("title") @@ -260,22 +267,22 @@ class FinderTest < ActiveRecord::TestCase def test_find_on_array_conditions assert Topic.find(1, :conditions => ["approved = ?", false]) - assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) } end def test_find_on_hash_conditions assert Topic.find(1, :conditions => { :approved => false }) - assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) } end def test_find_on_hash_conditions_with_explicit_table_name assert Topic.find(1, :conditions => { 'topics.approved' => false }) - assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) } end def test_find_on_hash_conditions_with_hashed_table_name assert Topic.find(1, :conditions => {:topics => { :approved => false }}) - assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => {:topics => { :approved => true }}) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => {:topics => { :approved => true }}) } end def test_find_with_hash_conditions_on_joined_table @@ -293,7 +300,7 @@ class FinderTest < ActiveRecord::TestCase 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_raises(ActiveRecord::RecordNotFound) { + assert_raise(ActiveRecord::RecordNotFound) { Customer.find(david.id, :conditions => { 'customers.name' => david.name + "1", :address => david.address }) } end @@ -304,13 +311,13 @@ class FinderTest < ActiveRecord::TestCase def test_find_on_hash_conditions_with_range assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1..2 }).map(&:id).sort - assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) } 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_raises(ActiveRecord::RecordNotFound) { Topic.find(3, :conditions => { :id => 2...3 }) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(3, :conditions => { :id => 2...3 }) } end def test_find_on_hash_conditions_with_multiple_ranges @@ -320,9 +327,9 @@ class FinderTest < ActiveRecord::TestCase 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_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) } - assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) } - assert_raises(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 => "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 }) } end def test_condition_interpolation @@ -346,7 +353,7 @@ class FinderTest < ActiveRecord::TestCase end def test_hash_condition_find_malformed - assert_raises(ActiveRecord::StatementInvalid) { + assert_raise(ActiveRecord::StatementInvalid) { Company.find(:first, :conditions => { :id => 2, :dhh => true }) } end @@ -415,10 +422,10 @@ class FinderTest < ActiveRecord::TestCase 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_raises(ActiveRecord::PreparedStatementInvalid) { + assert_raise(ActiveRecord::PreparedStatementInvalid) { Company.find(:first, :conditions => ["id=? AND name = ?", 2]) } - assert_raises(ActiveRecord::PreparedStatementInvalid) { + assert_raise(ActiveRecord::PreparedStatementInvalid) { Company.find(:first, :conditions => ["id=?", 2, 3, 4]) } end @@ -435,11 +442,11 @@ class FinderTest < ActiveRecord::TestCase def test_bind_arity assert_nothing_raised { bind '' } - assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '', 1 } + assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 } - assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?' } + assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' } assert_nothing_raised { bind '?', 1 } - assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 } + assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 } end def test_named_bind_variables @@ -544,7 +551,7 @@ class FinderTest < ActiveRecord::TestCase def test_find_by_one_attribute_bang assert_equal topics(:first), Topic.find_by_title!("The First Topic") - assert_raises(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") } + assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") } end def test_find_by_one_attribute_caches_dynamic_finder @@ -625,14 +632,14 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_one_missing_attribute - assert_raises(NoMethodError) { Topic.find_by_undertitle("The First Topic!") } + assert_raise(NoMethodError) { Topic.find_by_undertitle("The First Topic!") } end def test_find_by_invalid_method_syntax - assert_raises(NoMethodError) { Topic.fail_to_find_by_title("The First Topic") } - assert_raises(NoMethodError) { Topic.find_by_title?("The First Topic") } - assert_raises(NoMethodError) { Topic.fail_to_find_or_create_by_title("Nonexistent Title") } - assert_raises(NoMethodError) { Topic.find_or_create_by_title?("Nonexistent Title") } + assert_raise(NoMethodError) { Topic.fail_to_find_by_title("The First Topic") } + assert_raise(NoMethodError) { Topic.find_by_title?("The First Topic") } + assert_raise(NoMethodError) { Topic.fail_to_find_or_create_by_title("Nonexistent Title") } + assert_raise(NoMethodError) { Topic.find_or_create_by_title?("Nonexistent Title") } end def test_find_by_two_attributes @@ -654,8 +661,8 @@ class FinderTest < ActiveRecord::TestCase end def test_find_last_by_invalid_method_syntax - assert_raises(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") } - assert_raises(NoMethodError) { Topic.find_last_by_title?("The First Topic") } + 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 @@ -663,7 +670,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_last_by_one_missing_attribute - assert_raises(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") } + assert_raise(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") } end def test_find_last_by_two_attributes @@ -916,16 +923,16 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_bad_sql - assert_raises(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" } + assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" } end def test_find_with_invalid_params - assert_raises(ArgumentError) { Topic.find :first, :join => "It should be `joins'" } - assert_raises(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" } + 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_raises(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } + assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } end def test_find_all_with_limit @@ -1057,6 +1064,14 @@ class FinderTest < ActiveRecord::TestCase assert_equal [0, 1, 1], posts.map(&:author_id).sort end + def test_finder_with_scoped_from + all_topics = Topic.all + + Topic.with_scope(:find => { :from => 'fake_topics' }) do + assert_equal all_topics, Topic.all(:from => 'topics') + end + end + protected def bind(statement, *vars) if vars.first.is_a?(Hash) diff --git a/vendor/rails/activerecord/test/cases/fixtures_test.rb b/vendor/rails/activerecord/test/cases/fixtures_test.rb index eb63fd5f..252bf4ff 100644 --- a/vendor/rails/activerecord/test/cases/fixtures_test.rb +++ b/vendor/rails/activerecord/test/cases/fixtures_test.rb @@ -151,7 +151,7 @@ class FixturesTest < ActiveRecord::TestCase end def test_dirty_dirty_yaml_file - assert_raises(Fixture::FormatError) do + assert_raise(Fixture::FormatError) do Fixtures.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses") end end @@ -420,7 +420,7 @@ class InvalidTableNameFixturesTest < ActiveRecord::TestCase self.use_transactional_fixtures = false def test_raises_error - assert_raises FixtureClassNotFound do + assert_raise FixtureClassNotFound do funny_jokes(:a_joke) end end diff --git a/vendor/rails/activerecord/test/cases/inheritance_test.rb b/vendor/rails/activerecord/test/cases/inheritance_test.rb index 3f59eb97..eae5a608 100644 --- a/vendor/rails/activerecord/test/cases/inheritance_test.rb +++ b/vendor/rails/activerecord/test/cases/inheritance_test.rb @@ -68,7 +68,7 @@ class InheritanceTest < ActiveRecord::TestCase if current_adapter?(:SybaseAdapter) Company.connection.execute "SET IDENTITY_INSERT companies OFF" end - assert_raises(ActiveRecord::SubclassNotFound) { Company.find(100) } + assert_raise(ActiveRecord::SubclassNotFound) { Company.find(100) } end def test_inheritance_find @@ -124,7 +124,7 @@ class InheritanceTest < ActiveRecord::TestCase end def test_finding_incorrect_type_data - assert_raises(ActiveRecord::RecordNotFound) { Firm.find(2) } + assert_raise(ActiveRecord::RecordNotFound) { Firm.find(2) } assert_nothing_raised { Firm.find(1) } end diff --git a/vendor/rails/activerecord/test/cases/locking_test.rb b/vendor/rails/activerecord/test/cases/locking_test.rb index 077cac77..e1772355 100644 --- a/vendor/rails/activerecord/test/cases/locking_test.rb +++ b/vendor/rails/activerecord/test/cases/locking_test.rb @@ -35,7 +35,25 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, p2.lock_version p2.first_name = 'sue' - assert_raises(ActiveRecord::StaleObjectError) { p2.save! } + assert_raise(ActiveRecord::StaleObjectError) { p2.save! } + end + + def test_lock_destroy + p1 = Person.find(1) + p2 = Person.find(1) + assert_equal 0, p1.lock_version + assert_equal 0, p2.lock_version + + p1.first_name = 'stu' + p1.save! + assert_equal 1, p1.lock_version + assert_equal 0, p2.lock_version + + assert_raises(ActiveRecord::StaleObjectError) { p2.destroy } + + assert p1.destroy + assert_equal true, p1.frozen? + assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) } end def test_lock_repeating @@ -50,9 +68,9 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, p2.lock_version p2.first_name = 'sue' - assert_raises(ActiveRecord::StaleObjectError) { p2.save! } + assert_raise(ActiveRecord::StaleObjectError) { p2.save! } p2.first_name = 'sue2' - assert_raises(ActiveRecord::StaleObjectError) { p2.save! } + assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end def test_lock_new @@ -71,7 +89,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, p2.lock_version p2.first_name = 'sue' - assert_raises(ActiveRecord::StaleObjectError) { p2.save! } + assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end def test_lock_new_with_nil @@ -95,7 +113,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, t2.version t2.tps_report_number = 800 - assert_raises(ActiveRecord::StaleObjectError) { t2.save! } + assert_raise(ActiveRecord::StaleObjectError) { t2.save! } end def test_lock_column_is_mass_assignable diff --git a/vendor/rails/activerecord/test/cases/method_scoping_test.rb b/vendor/rails/activerecord/test/cases/method_scoping_test.rb index c676c1c7..3c34cdea 100644 --- a/vendor/rails/activerecord/test/cases/method_scoping_test.rb +++ b/vendor/rails/activerecord/test/cases/method_scoping_test.rb @@ -262,6 +262,15 @@ class NestedScopingTest < ActiveRecord::TestCase end end + def test_merge_inner_scope_has_priority + Developer.with_scope(:find => { :limit => 5 }) do + Developer.with_scope(:find => { :limit => 10 }) do + merged_option = Developer.instance_eval('current_scoped_methods')[:find] + assert_equal({ :limit => 10 }, merged_option) + end + end + end + def test_replace_options Developer.with_scope(:find => { :conditions => "name = 'David'" }) do Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do @@ -369,8 +378,10 @@ class NestedScopingTest < ActiveRecord::TestCase def test_merged_scoped_find poor_jamis = developers(:poor_jamis) Developer.with_scope(:find => { :conditions => "salary < 100000" }) do - Developer.with_scope(:find => { :offset => 1 }) do - assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc')) + Developer.with_scope(:find => { :offset => 1, :order => 'id asc' }) do + assert_sql /ORDER BY id asc / do + assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc')) + end end end end @@ -400,6 +411,29 @@ class NestedScopingTest < ActiveRecord::TestCase end end + def test_nested_scoped_create + comment = nil + Comment.with_scope(:create => { :post_id => 1}) do + Comment.with_scope(:create => { :post_id => 2}) do + assert_equal({ :post_id => 2 }, Comment.send(:current_scoped_methods)[: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.with_scope(:create => { :body => "Hey guys, nested scopes are broken. Please fix!" }) do + Comment.with_exclusive_scope(:create => { :post_id => 1 }) do + assert_equal({ :post_id => 1 }, Comment.send(:current_scoped_methods)[:create]) + 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.with_scope(:find => {:conditions => blank}) do @@ -523,7 +557,6 @@ class HasManyScopingTest< ActiveRecord::TestCase end end - class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase fixtures :posts, :categories, :categories_posts @@ -549,7 +582,6 @@ class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase end end - class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers @@ -577,7 +609,7 @@ class DefaultScopingTest < ActiveRecord::TestCase # Scopes added on children should append to parent scope expected_klass_scope = [{ :create => {}, :find => { :order => 'salary DESC' }}, { :create => {}, :find => {} }] assert_equal expected_klass_scope, klass.send(:scoped_methods) - + # Parent should still have the original scope assert_equal scope, DeveloperOrderedBySalary.send(:scoped_methods) end @@ -620,7 +652,6 @@ end =begin # We disabled the scoping for has_one and belongs_to as we can't think of a proper use case - class BelongsToScopingTest< ActiveRecord::TestCase fixtures :comments, :posts @@ -640,7 +671,6 @@ class BelongsToScopingTest< ActiveRecord::TestCase end - class HasOneScopingTest< ActiveRecord::TestCase fixtures :comments, :posts diff --git a/vendor/rails/activerecord/test/cases/migration_test.rb b/vendor/rails/activerecord/test/cases/migration_test.rb index 1c974e48..16861f21 100644 --- a/vendor/rails/activerecord/test/cases/migration_test.rb +++ b/vendor/rails/activerecord/test/cases/migration_test.rb @@ -93,6 +93,30 @@ if ActiveRecord::Base.connection.supports_migrations? end 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 + + def test_create_table_without_id + testing_table_with_only_foo_attribute do |connection| + 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| + assert_nothing_raised { connection.add_column :testings, :id, :primary_key } + assert_equal connection.columns(:testings).size, 2 + end + end + def test_create_table_adds_id Person.connection.create_table :testings do |t| t.column :foo, :string @@ -111,7 +135,7 @@ if ActiveRecord::Base.connection.supports_migrations? end end - assert_raises(ActiveRecord::StatementInvalid) do + assert_raise(ActiveRecord::StatementInvalid) do Person.connection.execute "insert into testings (foo) values (NULL)" end ensure @@ -278,7 +302,7 @@ if ActiveRecord::Base.connection.supports_migrations? end Person.connection.add_column :testings, :bar, :string, :null => false - assert_raises(ActiveRecord::StatementInvalid) do + assert_raise(ActiveRecord::StatementInvalid) do Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)" end ensure @@ -297,7 +321,7 @@ if ActiveRecord::Base.connection.supports_migrations? 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_raises(ActiveRecord::StatementInvalid) do + 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 @@ -545,7 +569,7 @@ if ActiveRecord::Base.connection.supports_migrations? else ActiveRecord::ActiveRecordError end - assert_raises(exception) do + assert_raise(exception) do Person.connection.rename_column "hats", "nonexistent", "should_fail" end ensure @@ -795,7 +819,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal "hello world", Reminder.find(:first).content WeNeedReminders.down - assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) } + assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } end def test_add_table_with_decimals @@ -856,7 +880,7 @@ if ActiveRecord::Base.connection.supports_migrations? end GiveMeBigNumbers.down - assert_raises(ActiveRecord::StatementInvalid) { BigNumber.find(:first) } + assert_raise(ActiveRecord::StatementInvalid) { BigNumber.find(:first) } end def test_migrator @@ -876,7 +900,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal 0, ActiveRecord::Migrator.current_version Person.reset_column_information assert !Person.column_methods_hash.include?(:last_name) - assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) } + assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } end def test_migrator_one_up @@ -928,11 +952,11 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal(0, ActiveRecord::Migrator.current_version) end - if current_adapter?(:PostgreSQLAdapter) + if ActiveRecord::Base.connection.supports_ddl_transactions? def test_migrator_one_up_with_exception_and_rollback assert !Person.column_methods_hash.include?(:last_name) - e = assert_raises(StandardError) do + e = assert_raise(StandardError) do ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/broken", 100) end @@ -945,20 +969,20 @@ if ActiveRecord::Base.connection.supports_migrations? def test_finds_migrations migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations - [['1', 'people_have_last_names'], - ['2', 'we_need_reminders'], - ['3', 'innocent_jointable']].each_with_index do |pair, i| - migrations[i].version == pair.first - migrations[1].name == pair.last + + [[1, 'PeopleHaveLastNames'], [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_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 - migrations[0].version == '3' - migrations[0].name == 'innocent_jointable' + assert_equal migrations[0].version, 3 + assert_equal migrations[0].name, 'InnocentJointable' end def test_only_loads_pending_migrations @@ -1107,7 +1131,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal "hello world", Reminder.find(:first).content WeNeedReminders.down - assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) } + assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } ensure ActiveRecord::Base.table_name_prefix = '' ActiveRecord::Base.table_name_suffix = '' @@ -1137,13 +1161,13 @@ if ActiveRecord::Base.connection.supports_migrations? end def test_migrator_with_duplicates - assert_raises(ActiveRecord::DuplicateMigrationVersionError) do + assert_raise(ActiveRecord::DuplicateMigrationVersionError) do ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate", nil) end end def test_migrator_with_duplicate_names - assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do + assert_raise(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate_names", nil) end end @@ -1159,7 +1183,7 @@ if ActiveRecord::Base.connection.supports_migrations? # table name is 29 chars, the standard sequence name will # be 33 chars and fail - assert_raises(ActiveRecord::StatementInvalid) do + assert_raise(ActiveRecord::StatementInvalid) do begin Person.connection.create_table :table_with_name_thats_just_ok do |t| t.column :foo, :string, :null => false @@ -1186,7 +1210,7 @@ if ActiveRecord::Base.connection.supports_migrations? end # confirm the custom sequence got dropped - assert_raises(ActiveRecord::StatementInvalid) do + assert_raise(ActiveRecord::StatementInvalid) do Person.connection.execute("select suitably_short_seq.nextval from dual") end end diff --git a/vendor/rails/activerecord/test/cases/named_scope_test.rb b/vendor/rails/activerecord/test/cases/named_scope_test.rb index f28285fa..ae6a54a5 100644 --- a/vendor/rails/activerecord/test/cases/named_scope_test.rb +++ b/vendor/rails/activerecord/test/cases/named_scope_test.rb @@ -15,7 +15,7 @@ class NamedScopeTest < ActiveRecord::TestCase 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.each { |i| i } + assert_equal Topic.find(:all), Topic.base.map { |i| i } end def test_found_items_are_cached @@ -99,6 +99,12 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal topics_written_before_the_second, Topic.written_before(topics(:second).written_on) end + def test_procedural_scopes_returning_nil + all_topics = Topic.find(: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( @@ -247,7 +253,7 @@ class NamedScopeTest < ActiveRecord::TestCase topic = Topic.approved.create!({}) assert topic.approved end - + def test_should_build_with_proxy_options_chained topic = Topic.approved.by_lifo.build({}) assert topic.approved @@ -287,15 +293,21 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size end - def test_chanining_should_use_latest_conditions_when_creating - post1 = Topic.rejected.approved.new - assert post1.approved? + def test_chaining_should_use_latest_conditions_when_creating + post = Topic.rejected.new + assert !post.approved? - post2 = Topic.approved.rejected.new - assert ! post2.approved? + post = Topic.rejected.approved.new + assert post.approved? + + post = Topic.approved.rejected.new + assert !post.approved? + + post = Topic.approved.rejected.approved.new + assert post.approved? end - def test_chanining_should_use_latest_conditions_when_searching + def test_chaining_should_use_latest_conditions_when_searching # Normal hash conditions assert_equal Topic.all(:conditions => {:approved => true}), Topic.rejected.approved.all assert_equal Topic.all(:conditions => {:approved => false}), Topic.approved.rejected.all @@ -306,6 +318,24 @@ class NamedScopeTest < ActiveRecord::TestCase # Nested hash conditions with different keys assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.uniq end + + def test_methods_invoked_within_scopes_should_respect_scope + assert_equal [], Topic.approved.by_rejected_ids.proxy_options[:conditions][:id] + end + + def test_named_scopes_batch_finders + assert_equal 3, Topic.approved.count + + assert_queries(4) do + Topic.approved.find_each(:batch_size => 1) {|t| assert t.approved? } + end + + assert_queries(2) do + Topic.approved.find_in_batches(:batch_size => 2) do |group| + group.each {|t| assert t.approved? } + end + end + end end class DynamicScopeMatchTest < ActiveRecord::TestCase diff --git a/vendor/rails/activerecord/test/cases/reflection_test.rb b/vendor/rails/activerecord/test/cases/reflection_test.rb index 8b1c714e..db64bbb8 100644 --- a/vendor/rails/activerecord/test/cases/reflection_test.rb +++ b/vendor/rails/activerecord/test/cases/reflection_test.rb @@ -4,6 +4,7 @@ require 'models/customer' require 'models/company' require 'models/company_in_module' require 'models/subscriber' +require 'models/pirate' class ReflectionTest < ActiveRecord::TestCase fixtures :topics, :customers, :companies, :subscribers @@ -169,9 +170,9 @@ class ReflectionTest < ActiveRecord::TestCase def test_reflection_of_all_associations # FIXME these assertions bust a lot - assert_equal 26, Firm.reflect_on_all_associations.size - assert_equal 20, Firm.reflect_on_all_associations(:has_many).size - assert_equal 6, Firm.reflect_on_all_associations(:has_one).size + assert_equal 28, Firm.reflect_on_all_associations.size + assert_equal 21, Firm.reflect_on_all_associations(:has_many).size + assert_equal 7, Firm.reflect_on_all_associations(:has_one).size assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size end diff --git a/vendor/rails/activerecord/test/cases/transactions_test.rb b/vendor/rails/activerecord/test/cases/transactions_test.rb index 40abd935..f6533b53 100644 --- a/vendor/rails/activerecord/test/cases/transactions_test.rb +++ b/vendor/rails/activerecord/test/cases/transactions_test.rb @@ -214,7 +214,7 @@ class TransactionTest < ActiveRecord::TestCase end def test_invalid_keys_for_transaction - assert_raises ArgumentError do + assert_raise ArgumentError do Topic.transaction :nested => true do end end @@ -349,7 +349,7 @@ class TransactionTest < ActiveRecord::TestCase end end - def test_sqlite_add_column_in_transaction_raises_statement_invalid + def test_sqlite_add_column_in_transaction return true unless current_adapter?(:SQLite3Adapter, :SQLiteAdapter) # Test first if column creation/deletion works correctly when no @@ -368,10 +368,15 @@ class TransactionTest < ActiveRecord::TestCase assert !Topic.column_names.include?('stuff') end - # Test now inside a transaction: add_column should raise a StatementInvalid - Topic.transaction do - assert_raises(ActiveRecord::StatementInvalid) { Topic.connection.add_column('topics', 'stuff', :string) } - raise ActiveRecord::Rollback + if Topic.connection.supports_ddl_transactions? + assert_nothing_raised do + Topic.transaction { Topic.connection.add_column('topics', 'stuff', :string) } + end + else + Topic.transaction do + assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.add_column('topics', 'stuff', :string) } + raise ActiveRecord::Rollback + end end end diff --git a/vendor/rails/activerecord/test/cases/validations_test.rb b/vendor/rails/activerecord/test/cases/validations_test.rb index cbb18413..c20f5ae6 100644 --- a/vendor/rails/activerecord/test/cases/validations_test.rb +++ b/vendor/rails/activerecord/test/cases/validations_test.rb @@ -8,6 +8,7 @@ require 'models/warehouse_thing' require 'models/guid' require 'models/owner' require 'models/pet' +require 'models/event' # The following methods in Topic are used in test_conditional_validation_* class Topic @@ -113,8 +114,8 @@ class ValidationsTest < ActiveRecord::TestCase end def test_invalid_record_exception - assert_raises(ActiveRecord::RecordInvalid) { Reply.create! } - assert_raises(ActiveRecord::RecordInvalid) { Reply.new.save! } + assert_raise(ActiveRecord::RecordInvalid) { Reply.create! } + assert_raise(ActiveRecord::RecordInvalid) { Reply.new.save! } begin r = Reply.new @@ -126,13 +127,13 @@ class ValidationsTest < ActiveRecord::TestCase end def test_exception_on_create_bang_many - assert_raises(ActiveRecord::RecordInvalid) do + assert_raise(ActiveRecord::RecordInvalid) do Reply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }]) end end def test_exception_on_create_bang_with_block - assert_raises(ActiveRecord::RecordInvalid) do + assert_raise(ActiveRecord::RecordInvalid) do Reply.create!({ "title" => "OK" }) do |r| r.content = nil end @@ -140,7 +141,7 @@ class ValidationsTest < ActiveRecord::TestCase end def test_exception_on_create_bang_many_with_block - assert_raises(ActiveRecord::RecordInvalid) do + assert_raise(ActiveRecord::RecordInvalid) do Reply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r| r.content = nil end @@ -149,7 +150,7 @@ class ValidationsTest < ActiveRecord::TestCase def test_scoped_create_without_attributes Reply.with_scope(:create => {}) do - assert_raises(ActiveRecord::RecordInvalid) { Reply.create! } + assert_raise(ActiveRecord::RecordInvalid) { Reply.create! } end end @@ -169,7 +170,7 @@ class ValidationsTest < ActiveRecord::TestCase assert_equal person.first_name, "Mary", "should be ok when no attributes are passed to create!" end end - end + end def test_single_error_per_attr_iteration r = Reply.new @@ -530,6 +531,14 @@ class ValidationsTest < ActiveRecord::TestCase end end + def test_validate_uniqueness_with_limit + # Event.title is limited to 5 characters + e1 = Event.create(:title => "abcde") + assert e1.valid?, "Could not create an event with a unique, 5 character title" + e2 = Event.create(:title => "abcdefgh") + assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" + end + def test_validate_straight_inheritance_uniqueness w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork") assert w1.valid?, "Saving w1" @@ -1421,6 +1430,17 @@ class ValidationsTest < ActiveRecord::TestCase assert_equal "can't be blank", t.errors.on("title").first end + def test_invalid_should_be_the_opposite_of_valid + Topic.validates_presence_of :title + + t = Topic.new + assert t.invalid? + assert t.errors.invalid?(:title) + + t.title = 'Things are going to change' + assert !t.invalid? + end + # previous implementation of validates_presence_of eval'd the # string with the wrong binding, this regression test is to # ensure that it works correctly @@ -1442,20 +1462,6 @@ class ValidationsTest < ActiveRecord::TestCase t.author_name = "Hubert J. Farnsworth" assert t.valid?, "A topic with an important title and author should be valid" end - - private - 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 diff --git a/vendor/rails/activerecord/test/cases/xml_serialization_test.rb b/vendor/rails/activerecord/test/cases/xml_serialization_test.rb index 39c6ea82..b4999766 100644 --- a/vendor/rails/activerecord/test/cases/xml_serialization_test.rb +++ b/vendor/rails/activerecord/test/cases/xml_serialization_test.rb @@ -38,11 +38,15 @@ class XmlSerializationTest < ActiveRecord::TestCase assert_match %r{ 25).to_xml :skip_types => true + assert %r{25}.match(@xml) + end + def test_should_include_yielded_additions @xml = Contact.new.to_xml do |xml| xml.creator "David" end - assert_match %r{David}, @xml end end @@ -145,6 +149,13 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase assert_match %r{}, xml end + def test_included_associations_should_skip_types + xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0, :skip_types => true + assert_match %r{}, xml + assert_match %r{}, xml + assert_match %r{}, xml + end + def test_methods_are_called_on_object xml = authors(:david).to_xml :methods => :label, :indent => 0 assert_match %r{}, xml diff --git a/vendor/rails/activerecord/test/fixtures/toys.yml b/vendor/rails/activerecord/test/fixtures/toys.yml new file mode 100644 index 00000000..037e335e --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/toys.yml @@ -0,0 +1,4 @@ +bone: + toy_id: 1 + name: Bone + pet_id: 1 diff --git a/vendor/rails/activerecord/test/models/company.rb b/vendor/rails/activerecord/test/models/company.rb index 3b27a9e2..02a775f9 100644 --- a/vendor/rails/activerecord/test/models/company.rb +++ b/vendor/rails/activerecord/test/models/company.rb @@ -37,6 +37,7 @@ class Firm < Company has_many :clients, :order => "id", :dependent => :destroy, :counter_sql => "SELECT COUNT(*) FROM companies WHERE firm_id = 1 " + "AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )" + has_many :unsorted_clients, :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 :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false @@ -69,6 +70,7 @@ class Firm < Company has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account' has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account" + has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete end class DependentFirm < Company diff --git a/vendor/rails/activerecord/test/models/event.rb b/vendor/rails/activerecord/test/models/event.rb new file mode 100644 index 00000000..99fa0fee --- /dev/null +++ b/vendor/rails/activerecord/test/models/event.rb @@ -0,0 +1,3 @@ +class Event < ActiveRecord::Base + validates_uniqueness_of :title +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/models/owner.rb b/vendor/rails/activerecord/test/models/owner.rb index dbaf2ce6..5760b991 100644 --- a/vendor/rails/activerecord/test/models/owner.rb +++ b/vendor/rails/activerecord/test/models/owner.rb @@ -1,4 +1,5 @@ class Owner < ActiveRecord::Base set_primary_key :owner_id has_many :pets -end \ No newline at end of file + has_many :toys, :through => :pets +end diff --git a/vendor/rails/activerecord/test/models/pet.rb b/vendor/rails/activerecord/test/models/pet.rb index 889ce46f..dc1a3c5e 100644 --- a/vendor/rails/activerecord/test/models/pet.rb +++ b/vendor/rails/activerecord/test/models/pet.rb @@ -1,4 +1,5 @@ class Pet < ActiveRecord::Base set_primary_key :pet_id belongs_to :owner -end \ No newline at end of file + has_many :toys +end diff --git a/vendor/rails/activerecord/test/models/pirate.rb b/vendor/rails/activerecord/test/models/pirate.rb index 7bc50e0e..238917bf 100644 --- a/vendor/rails/activerecord/test/models/pirate.rb +++ b/vendor/rails/activerecord/test/models/pirate.rb @@ -1,16 +1,63 @@ class Pirate < ActiveRecord::Base belongs_to :parrot has_and_belongs_to_many :parrots - has_many :treasures, :as => :looter + has_and_belongs_to_many :parrots_with_method_callbacks, :class_name => "Parrot", + :before_add => :log_before_add, + :after_add => :log_after_add, + :before_remove => :log_before_remove, + :after_remove => :log_after_remove + has_and_belongs_to_many :parrots_with_proc_callbacks, :class_name => "Parrot", + :before_add => proc {|p,pa| p.ship_log << "before_adding_proc_parrot_#{pa.id || ''}"}, + :after_add => proc {|p,pa| p.ship_log << "after_adding_proc_parrot_#{pa.id || ''}"}, + :before_remove => proc {|p,pa| p.ship_log << "before_removing_proc_parrot_#{pa.id}"}, + :after_remove => proc {|p,pa| p.ship_log << "after_removing_proc_parrot_#{pa.id}"} + has_many :treasures, :as => :looter has_many :treasure_estimates, :through => :treasures, :source => :price_estimates # These both have :autosave enabled because accepts_nested_attributes_for is used on them. has_one :ship has_many :birds + has_many :birds_with_method_callbacks, :class_name => "Bird", + :before_add => :log_before_add, + :after_add => :log_after_add, + :before_remove => :log_before_remove, + :after_remove => :log_after_remove + has_many :birds_with_proc_callbacks, :class_name => "Bird", + :before_add => proc {|p,b| p.ship_log << "before_adding_proc_bird_#{b.id || ''}"}, + :after_add => proc {|p,b| p.ship_log << "after_adding_proc_bird_#{b.id || ''}"}, + :before_remove => proc {|p,b| p.ship_log << "before_removing_proc_bird_#{b.id}"}, + :after_remove => proc {|p,b| p.ship_log << "after_removing_proc_bird_#{b.id}"} accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } + accepts_nested_attributes_for :parrots_with_method_callbacks, :parrots_with_proc_callbacks, + :birds_with_method_callbacks, :birds_with_proc_callbacks, :allow_destroy => true validates_presence_of :catchphrase + + def ship_log + @ship_log ||= [] + end + + private + def log_before_add(record) + log(record, "before_adding_method") + end + + def log_after_add(record) + log(record, "after_adding_method") + end + + def log_before_remove(record) + log(record, "before_removing_method") + end + + def log_after_remove(record) + log(record, "after_removing_method") + end + + def log(record, callback) + ship_log << "#{callback}_#{record.class.name.downcase}_#{record.id || ''}" + end end diff --git a/vendor/rails/activerecord/test/models/reply.rb b/vendor/rails/activerecord/test/models/reply.rb index 812bc1f5..1c990aca 100644 --- a/vendor/rails/activerecord/test/models/reply.rb +++ b/vendor/rails/activerecord/test/models/reply.rb @@ -37,3 +37,9 @@ end class SillyReply < Reply belongs_to :reply, :foreign_key => "parent_id", :counter_cache => :replies_count end + +module Web + class Reply < Web::Topic + belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true, :class_name => 'Web::Topic' + end +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/models/topic.rb b/vendor/rails/activerecord/test/models/topic.rb index 08bb24ed..51012d22 100644 --- a/vendor/rails/activerecord/test/models/topic.rb +++ b/vendor/rails/activerecord/test/models/topic.rb @@ -1,7 +1,9 @@ class Topic < ActiveRecord::Base named_scope :base named_scope :written_before, lambda { |time| - { :conditions => ['written_on < ?', time] } + if time + { :conditions => ['written_on < ?', time] } + end } named_scope :approved, :conditions => {:approved => true} named_scope :rejected, :conditions => {:approved => false} @@ -33,6 +35,8 @@ class Topic < ActiveRecord::Base end named_scope :named_extension, :extend => NamedExtension named_scope :multiple_extensions, :extend => [MultipleExtensionTwo, MultipleExtensionOne] + + named_scope :by_rejected_ids, lambda {{ :conditions => { :id => all(:conditions => {:approved => false}).map(&:id) } }} has_many :replies, :dependent => :destroy, :foreign_key => "parent_id" serialize :content @@ -69,3 +73,9 @@ class Topic < ActiveRecord::Base end end end + +module Web + class Topic < ActiveRecord::Base + has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply' + end +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/models/toy.rb b/vendor/rails/activerecord/test/models/toy.rb new file mode 100644 index 00000000..79a88db0 --- /dev/null +++ b/vendor/rails/activerecord/test/models/toy.rb @@ -0,0 +1,4 @@ +class Toy < ActiveRecord::Base + set_primary_key :toy_id + belongs_to :pet +end diff --git a/vendor/rails/activerecord/test/schema/schema.rb b/vendor/rails/activerecord/test/schema/schema.rb index 74a89398..ea848a29 100644 --- a/vendor/rails/activerecord/test/schema/schema.rb +++ b/vendor/rails/activerecord/test/schema/schema.rb @@ -155,6 +155,10 @@ ActiveRecord::Schema.define do t.integer :course_id, :null => false end + create_table :events, :force => true do |t| + t.string :title, :limit => 5 + end + create_table :funny_jokes, :force => true do |t| t.string :name end @@ -421,6 +425,11 @@ ActiveRecord::Schema.define do t.column :taggings_count, :integer, :default => 0 end + create_table :toys, :primary_key => :toy_id ,:force => true do |t| + t.string :name + t.integer :pet_id, :integer + end + create_table :treasures, :force => true do |t| t.column :name, :string t.column :looter_id, :integer diff --git a/vendor/rails/activeresource/CHANGELOG b/vendor/rails/activeresource/CHANGELOG index ea0f7b1b..65729348 100644 --- a/vendor/rails/activeresource/CHANGELOG +++ b/vendor/rails/activeresource/CHANGELOG @@ -1,11 +1,6 @@ -*2.3.1 [RC2] (March 5, 2009)* +*2.3.2 [Final] (March 15, 2009)* -* Nothing new, just included in 2.3.1 - - -*2.3.0 [RC1] (February 1st, 2009)* - -* Nothing new, just included in 2.3.0 +* Nothing new, just included in 2.3.2 *2.2.1 [RC2] (November 14th, 2008)* diff --git a/vendor/rails/activeresource/Rakefile b/vendor/rails/activeresource/Rakefile index fc67b520..bf7bbb02 100644 --- a/vendor/rails/activeresource/Rakefile +++ b/vendor/rails/activeresource/Rakefile @@ -67,7 +67,7 @@ spec = Gem::Specification.new do |s| s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) } end - s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'active_resource' diff --git a/vendor/rails/activeresource/lib/active_resource/version.rb b/vendor/rails/activeresource/lib/active_resource/version.rb index 4ac7eb1c..3df2555d 100644 --- a/vendor/rails/activeresource/lib/active_resource/version.rb +++ b/vendor/rails/activeresource/lib/active_resource/version.rb @@ -2,7 +2,7 @@ module ActiveResource module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/activeresource/test/abstract_unit.rb b/vendor/rails/activeresource/test/abstract_unit.rb index 83bf1ed5..0f11ea48 100644 --- a/vendor/rails/activeresource/test/abstract_unit.rb +++ b/vendor/rails/activeresource/test/abstract_unit.rb @@ -5,6 +5,7 @@ gem 'mocha', '>= 0.9.5' require 'mocha' $:.unshift "#{File.dirname(__FILE__)}/../lib" +$:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib" require 'active_resource' require 'active_resource/http_mock' diff --git a/vendor/rails/activeresource/test/authorization_test.rb b/vendor/rails/activeresource/test/authorization_test.rb index ead7f5c1..ca25f437 100644 --- a/vendor/rails/activeresource/test/authorization_test.rb +++ b/vendor/rails/activeresource/test/authorization_test.rb @@ -107,10 +107,10 @@ class AuthorizationTest < Test::Unit::TestCase end def test_raises_invalid_request_on_unauthorized_requests - assert_raises(ActiveResource::InvalidRequestError) { @conn.post("/people/2.xml") } - assert_raises(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") } - assert_raises(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") } - assert_raises(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") } end protected diff --git a/vendor/rails/activeresource/test/base_test.rb b/vendor/rails/activeresource/test/base_test.rb index e22388f4..6ed6f1a4 100644 --- a/vendor/rails/activeresource/test/base_test.rb +++ b/vendor/rails/activeresource/test/base_test.rb @@ -47,7 +47,7 @@ class BaseTest < Test::Unit::TestCase {:name => 'Milena', :children => []}]}]}.to_xml(:root => 'customer') # - resource with yaml array of strings; for ActiveRecords using serialize :bar, Array - @marty = <<-eof + @marty = <<-eof.strip 5 @@ -564,14 +564,14 @@ class BaseTest < Test::Unit::TestCase def test_custom_header Person.headers['key'] = 'value' - assert_raises(ActiveResource::ResourceNotFound) { Person.find(4) } + assert_raise(ActiveResource::ResourceNotFound) { Person.find(4) } ensure Person.headers.delete('key') end def test_find_by_id_not_found - assert_raises(ActiveResource::ResourceNotFound) { Person.find(99) } - assert_raises(ActiveResource::ResourceNotFound) { StreetAddress.find(1) } + assert_raise(ActiveResource::ResourceNotFound) { Person.find(99) } + assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1) } end def test_find_all_by_from @@ -689,7 +689,7 @@ class BaseTest < Test::Unit::TestCase ActiveResource::HttpMock.respond_to do |mock| mock.post "/people.xml", {}, nil, 409 end - assert_raises(ActiveResource::ResourceConflict) { Person.create(:name => 'Rick') } + assert_raise(ActiveResource::ResourceConflict) { Person.create(:name => 'Rick') } end def test_create_without_location @@ -726,7 +726,7 @@ class BaseTest < Test::Unit::TestCase matz.non_ar_arr = ["not", "ARes"] matz_c = matz.clone assert matz_c.new? - assert_raises(NoMethodError) {matz_c.address} + 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 @@ -764,7 +764,7 @@ class BaseTest < Test::Unit::TestCase mock.get "/people/2.xml", {}, @david mock.put "/people/2.xml", @default_request_headers, nil, 409 end - assert_raises(ActiveResource::ResourceConflict) { Person.find(2).save } + assert_raise(ActiveResource::ResourceConflict) { Person.find(2).save } end def test_destroy @@ -772,7 +772,7 @@ class BaseTest < Test::Unit::TestCase ActiveResource::HttpMock.respond_to do |mock| mock.get "/people/1.xml", {}, nil, 404 end - assert_raises(ActiveResource::ResourceNotFound) { Person.find(1).destroy } + assert_raise(ActiveResource::ResourceNotFound) { Person.find(1).destroy } end def test_destroy_with_custom_prefix @@ -780,7 +780,7 @@ class BaseTest < Test::Unit::TestCase ActiveResource::HttpMock.respond_to do |mock| mock.get "/people/1/addresses/1.xml", {}, nil, 404 end - assert_raises(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) } + assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) } end def test_delete @@ -788,7 +788,7 @@ class BaseTest < Test::Unit::TestCase ActiveResource::HttpMock.respond_to do |mock| mock.get "/people/1.xml", {}, nil, 404 end - assert_raises(ActiveResource::ResourceNotFound) { Person.find(1) } + assert_raise(ActiveResource::ResourceNotFound) { Person.find(1) } end def test_delete_with_custom_prefix @@ -796,7 +796,7 @@ class BaseTest < Test::Unit::TestCase ActiveResource::HttpMock.respond_to do |mock| mock.get "/people/1/addresses/1.xml", {}, nil, 404 end - assert_raises(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) } + assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) } end def test_exists diff --git a/vendor/rails/activesupport/CHANGELOG b/vendor/rails/activesupport/CHANGELOG index bbece634..ab40e1a1 100644 --- a/vendor/rails/activesupport/CHANGELOG +++ b/vendor/rails/activesupport/CHANGELOG @@ -1,4 +1,7 @@ -*2.3.1 [RC2] (March 5, 2009)* +*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] @@ -12,9 +15,6 @@ * 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] - -*2.3.0 [RC1] (February 1st, 2009)* - * 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] diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/date/calculations.rb b/vendor/rails/activesupport/lib/active_support/core_ext/date/calculations.rb index 43d70c70..7f94da01 100644 --- a/vendor/rails/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/vendor/rails/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -1,7 +1,7 @@ module ActiveSupport #:nodoc: module CoreExtensions #:nodoc: module Date #:nodoc: - # Enables the use of time calculations within Time itself + # Enables the use of time calculations within Date itself module Calculations def self.included(base) #:nodoc: base.extend ClassMethods diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/hash/conversions.rb b/vendor/rails/activesupport/lib/active_support/core_ext/hash/conversions.rb index 991a5a6a..10435975 100644 --- a/vendor/rails/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/vendor/rails/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -24,11 +24,12 @@ module ActiveSupport #:nodoc: "Bignum" => "integer", "BigDecimal" => "decimal", "Float" => "float", + "TrueClass" => "boolean", + "FalseClass" => "boolean", "Date" => "date", "DateTime" => "datetime", "Time" => "datetime", - "TrueClass" => "boolean", - "FalseClass" => "boolean" + "ActiveSupport::TimeWithZone" => "datetime" } unless defined?(XML_TYPE_NAMES) XML_FORMATTING = { diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/vendor/rails/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index c96c5160..34ba8a00 100644 --- a/vendor/rails/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/vendor/rails/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -91,6 +91,12 @@ class HashWithIndifferentAccess < Hash self.dup.update(hash) end + # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second. + # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess. + def reverse_merge(other_hash) + super other_hash.with_indifferent_access + end + # Removes a specified key from the hash. def delete(key) super(convert_key(key)) diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/string/inflections.rb b/vendor/rails/activesupport/lib/active_support/core_ext/string/inflections.rb index 15ad3d99..48e812aa 100644 --- a/vendor/rails/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/vendor/rails/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -102,8 +102,8 @@ module ActiveSupport #:nodoc: # # <%= link_to(@person.name, person_path %> # # => Donald E. Knuth - def parameterize - Inflector.parameterize(self) + def parameterize(sep = '-') + Inflector.parameterize(self, sep) end # Creates the name of a table like Rails does for models to table names. This method diff --git a/vendor/rails/activesupport/lib/active_support/inflector.rb b/vendor/rails/activesupport/lib/active_support/inflector.rb index 5ff6f50f..3ed30bdf 100644 --- a/vendor/rails/activesupport/lib/active_support/inflector.rb +++ b/vendor/rails/activesupport/lib/active_support/inflector.rb @@ -257,15 +257,17 @@ module ActiveSupport # <%= link_to(@person.name, person_path(@person)) %> # # => Donald E. Knuth def parameterize(string, sep = '-') - re_sep = Regexp.escape(sep) # replace accented chars with ther ascii equivalents parameterized_string = transliterate(string) # Turn unwanted chars into the seperator parameterized_string.gsub!(/[^a-z0-9\-_\+]+/i, sep) - # No more than one of the separator in a row. - parameterized_string.squeeze!(sep) - # Remove leading/trailing separator. - parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '') + unless sep.blank? + re_sep = Regexp.escape(sep) + # No more than one of the separator in a row. + parameterized_string.gsub!(/#{re_sep}{2,}/, sep) + # Remove leading/trailing separator. + parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '') + end parameterized_string.downcase end diff --git a/vendor/rails/activesupport/lib/active_support/json/decoding.rb b/vendor/rails/activesupport/lib/active_support/json/decoding.rb index 5eb8c0fd..0e079341 100644 --- a/vendor/rails/activesupport/lib/active_support/json/decoding.rb +++ b/vendor/rails/activesupport/lib/active_support/json/decoding.rb @@ -43,14 +43,32 @@ module ActiveSupport end if marks.empty? - json.gsub(/\\\//, '/') + json.gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do + ustr = $1 + if ustr.starts_with?('u') + [ustr[1..-1].to_i(16)].pack("U") + elsif ustr == '\\' + '\\\\' + else + ustr + end + end else left_pos = [-1].push(*marks) right_pos = marks << scanner.pos + scanner.rest_size output = [] left_pos.each_with_index do |left, i| scanner.pos = left.succ - output << scanner.peek(right_pos[i] - scanner.pos + 1) + output << scanner.peek(right_pos[i] - scanner.pos + 1).gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do + ustr = $1 + if ustr.starts_with?('u') + [ustr[1..-1].to_i(16)].pack("U") + elsif ustr == '\\' + '\\\\' + else + ustr + end + end end output = output * " " diff --git a/vendor/rails/activesupport/lib/active_support/memoizable.rb b/vendor/rails/activesupport/lib/active_support/memoizable.rb index 952b4d80..71cfe617 100644 --- a/vendor/rails/activesupport/lib/active_support/memoizable.rb +++ b/vendor/rails/activesupport/lib/active_support/memoizable.rb @@ -88,6 +88,10 @@ module ActiveSupport #{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 end # end EOS end diff --git a/vendor/rails/activesupport/lib/active_support/multibyte/chars.rb b/vendor/rails/activesupport/lib/active_support/multibyte/chars.rb index 62b6d798..60f082bc 100644 --- a/vendor/rails/activesupport/lib/active_support/multibyte/chars.rb +++ b/vendor/rails/activesupport/lib/active_support/multibyte/chars.rb @@ -344,7 +344,19 @@ module ActiveSupport #:nodoc: end alias_method :[], :slice - # Converts first character in the string to Unicode value + # Like String#slice!, except instead of byte offsets you specify character offsets. + # + # Example: + # s = 'こんにちは' + # s.mb_chars.slice!(2..3).to_s #=> "にち" + # s #=> "こんは" + def slice!(*args) + slice = self[*args] + self[*args] = '' + slice + end + + # Returns the codepoint of the first character in the string. # # Example: # 'こんにちは'.mb_chars.ord #=> 12371 @@ -432,7 +444,7 @@ module ActiveSupport #:nodoc: chars(self.class.tidy_bytes(@wrapped_string)) end - %w(lstrip rstrip strip reverse upcase downcase slice tidy_bytes capitalize).each do |method| + %w(lstrip rstrip strip reverse upcase downcase tidy_bytes capitalize).each do |method| define_method("#{method}!") do |*args| unless args.nil? @wrapped_string = send(method, *args).to_s diff --git a/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb b/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb index 7edf6fdb..aaf9f8f4 100644 --- a/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -45,7 +45,12 @@ module ActiveSupport return if @method_name.to_s == "default_test" if using_mocha = respond_to?(:mocha_verify) - assertion_counter = Mocha::TestCaseAdapter::AssertionCounter.new(result) + assertion_counter_klass = if defined?(Mocha::TestCaseAdapter::AssertionCounter) + Mocha::TestCaseAdapter::AssertionCounter + else + Mocha::Integration::TestUnit::AssertionCounter + end + assertion_counter = assertion_counter_klass.new(result) end yield(Test::Unit::TestCase::STARTED, name) diff --git a/vendor/rails/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb b/vendor/rails/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb index bbb35ec8..50d6832c 100644 --- a/vendor/rails/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb +++ b/vendor/rails/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb @@ -116,10 +116,10 @@ class I18nTest < Test::Unit::TestCase end def test_localize_nil_raises_argument_error - assert_raises(I18n::ArgumentError) { I18n.l nil } + assert_raise(I18n::ArgumentError) { I18n.l nil } end def test_localize_object_raises_argument_error - assert_raises(I18n::ArgumentError) { I18n.l Object.new } + assert_raise(I18n::ArgumentError) { I18n.l Object.new } end end diff --git a/vendor/rails/activesupport/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb b/vendor/rails/activesupport/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb index 7b7b1375..65f3ac11 100644 --- a/vendor/rails/activesupport/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb +++ b/vendor/rails/activesupport/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb @@ -155,7 +155,7 @@ class I18nSimpleBackendTranslateTest < Test::Unit::TestCase end def test_translate_given_an_array_of_inexistent_keys_it_raises_missing_translation_data - assert_raises I18n::MissingTranslationData do + assert_raise I18n::MissingTranslationData do @backend.translate('en', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :does_not_exist_3]) end end @@ -180,11 +180,11 @@ class I18nSimpleBackendTranslateTest < Test::Unit::TestCase end def test_translate_given_nil_as_a_locale_raises_an_argument_error - assert_raises(I18n::InvalidLocale){ @backend.translate nil, :bar } + assert_raise(I18n::InvalidLocale){ @backend.translate nil, :bar } end def test_translate_with_a_bogus_key_and_no_default_raises_missing_translation_data - assert_raises(I18n::MissingTranslationData){ @backend.translate 'de', :bogus } + assert_raise(I18n::MissingTranslationData){ @backend.translate 'de', :bogus } end end @@ -230,15 +230,15 @@ class I18nSimpleBackendPluralizeTest < Test::Unit::TestCase end def test_interpolate_given_incomplete_pluralization_data_raises_invalid_pluralization_data - assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, {:one => 'bar'}, 2) } + assert_raise(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, {:one => 'bar'}, 2) } end # def test_interpolate_given_a_string_raises_invalid_pluralization_data - # assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, 'bar', 2) } + # assert_raise(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, 'bar', 2) } # end # # def test_interpolate_given_an_array_raises_invalid_pluralization_data - # assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, ['bar'], 2) } + # assert_raise(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, ['bar'], 2) } # end end @@ -267,13 +267,13 @@ class I18nSimpleBackendInterpolateTest < Test::Unit::TestCase end def test_interpolate_given_an_unicode_value_hash_into_a_non_unicode_multibyte_string_raises_encoding_compatibility_error - assert_raises(Encoding::CompatibilityError) do + assert_raise(Encoding::CompatibilityError) do @backend.send(:interpolate, nil, euc_jp('こんにちは、{{name}}さん!'), :name => 'ゆきひろ') end end def test_interpolate_given_a_non_unicode_multibyte_value_hash_into_an_unicode_string_raises_encoding_compatibility_error - assert_raises(Encoding::CompatibilityError) do + assert_raise(Encoding::CompatibilityError) do @backend.send(:interpolate, nil, 'こんにちは、{{name}}さん!', :name => euc_jp('ゆきひろ')) end end @@ -292,11 +292,11 @@ class I18nSimpleBackendInterpolateTest < Test::Unit::TestCase end def test_interpolate_given_an_empty_values_hash_raises_missing_interpolation_argument - assert_raises(I18n::MissingInterpolationArgument) { @backend.send(:interpolate, nil, 'Hi {{name}}!', {}) } + assert_raise(I18n::MissingInterpolationArgument) { @backend.send(:interpolate, nil, 'Hi {{name}}!', {}) } end def test_interpolate_given_a_string_containing_a_reserved_key_raises_reserved_interpolation_key - assert_raises(I18n::ReservedInterpolationKey) { @backend.send(:interpolate, nil, '{{default}}', {:default => nil}) } + assert_raise(I18n::ReservedInterpolationKey) { @backend.send(:interpolate, nil, '{{default}}', {:default => nil}) } end private @@ -352,11 +352,11 @@ class I18nSimpleBackendLocalizeDateTest < Test::Unit::TestCase end def test_localize_nil_raises_argument_error - assert_raises(I18n::ArgumentError) { @backend.localize 'de', nil } + assert_raise(I18n::ArgumentError) { @backend.localize 'de', nil } end def test_localize_object_raises_argument_error - assert_raises(I18n::ArgumentError) { @backend.localize 'de', Object.new } + assert_raise(I18n::ArgumentError) { @backend.localize 'de', Object.new } end end @@ -486,7 +486,7 @@ class I18nSimpleBackendLoadTranslationsTest < Test::Unit::TestCase include I18nSimpleBackendTestSetup def test_load_translations_with_unknown_file_type_raises_exception - assert_raises(I18n::UnknownFileType) { @backend.load_translations "#{@locale_dir}/en.xml" } + assert_raise(I18n::UnknownFileType) { @backend.load_translations "#{@locale_dir}/en.xml" } end def test_load_translations_with_ruby_file_type_does_not_raise_exception diff --git a/vendor/rails/activesupport/lib/active_support/version.rb b/vendor/rails/activesupport/lib/active_support/version.rb index 8b65fffa..30f598a8 100644 --- a/vendor/rails/activesupport/lib/active_support/version.rb +++ b/vendor/rails/activesupport/lib/active_support/version.rb @@ -2,7 +2,7 @@ module ActiveSupport module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/activesupport/lib/active_support/xml_mini.rb b/vendor/rails/activesupport/lib/active_support/xml_mini.rb index ce3f5062..ccd13494 100644 --- a/vendor/rails/activesupport/lib/active_support/xml_mini.rb +++ b/vendor/rails/activesupport/lib/active_support/xml_mini.rb @@ -1,113 +1,31 @@ -# = XmlMini -# This is a derivitive work of XmlSimple 1.0.11 -# Author:: Joseph Holsten -# Copyright:: Copyright (c) 2008 Joseph Holsten -# Copyright:: Copyright (c) 2003-2006 Maik Schmidt -# License:: Distributes under the same terms as Ruby. module ActiveSupport + # = XmlMini + # + # To use the much faster libxml parser: + # gem 'libxml-ruby', '=0.9.7' + # XmlMini.backend = 'LibXML' module XmlMini extend self - CONTENT_KEY = '__content__'.freeze + attr_reader :backend + delegate :parse, :to => :backend - # Parse an XML Document string into a simple hash - # - # Same as XmlSimple::xml_in but doesn't shoot itself in the foot, - # and uses the defaults from ActiveSupport - # - # string:: - # XML Document string to parse - def parse(string) - require 'rexml/document' unless defined?(REXML::Document) - doc = REXML::Document.new(string) - merge_element!({}, doc.root) + def backend=(name) + if name.is_a?(Module) + @backend = name + else + require "active_support/xml_mini/#{name.to_s.downcase}.rb" + @backend = ActiveSupport.const_get("XmlMini_#{name}") + end end - private - # Convert an XML element and merge into the hash - # - # hash:: - # Hash to merge the converted element into. - # element:: - # XML element to merge into hash - def merge_element!(hash, element) - merge!(hash, element.name, collapse(element)) - end - - # Actually converts an XML document element into a data structure. - # - # element:: - # The document element to be collapsed. - def collapse(element) - hash = get_attributes(element) - - if element.has_elements? - element.each_element {|child| merge_element!(hash, child) } - merge_texts!(hash, element) unless empty_content?(element) - hash - else - merge_texts!(hash, element) - end - end - - # Merge all the texts of an element into the hash - # - # hash:: - # Hash to add the converted emement to. - # element:: - # XML element whose texts are to me merged into the hash - def merge_texts!(hash, element) - unless element.has_text? - hash - else - # must use value to prevent double-escaping - merge!(hash, CONTENT_KEY, element.texts.sum(&:value)) - end - end - - # Adds a new key/value pair to an existing Hash. If the key to be added - # already exists and the existing value associated with key is not - # an Array, it will be wrapped in an Array. Then the new value is - # appended to that Array. - # - # hash:: - # Hash to add key/value pair to. - # key:: - # Key to be added. - # value:: - # Value to be associated with key. - def merge!(hash, key, value) - if hash.has_key?(key) - if hash[key].instance_of?(Array) - hash[key] << value - else - hash[key] = [hash[key], value] - end - elsif value.instance_of?(Array) - hash[key] = [value] - else - hash[key] = value - end - hash - end - - # Converts the attributes array of an XML element into a hash. - # Returns an empty Hash if node has no attributes. - # - # element:: - # XML element to extract attributes from. - def get_attributes(element) - attributes = {} - element.attributes.each { |n,v| attributes[n] = v } - attributes - end - - # Determines if a document element has text content - # - # element:: - # XML element to be checked. - def empty_content?(element) - element.texts.join.blank? - end + def with_backend(name) + old_backend, self.backend = backend, name + yield + ensure + self.backend = old_backend + end end -end \ No newline at end of file + + XmlMini.backend = 'REXML' +end diff --git a/vendor/rails/activesupport/lib/active_support/xml_mini/libxml.rb b/vendor/rails/activesupport/lib/active_support/xml_mini/libxml.rb new file mode 100644 index 00000000..3586b24a --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/xml_mini/libxml.rb @@ -0,0 +1,133 @@ +require 'libxml' + +# = XmlMini LibXML implementation +module ActiveSupport + module XmlMini_LibXML #:nodoc: + extend self + + # Parse an XML Document string into a simple hash using libxml. + # string:: + # XML Document string to parse + def parse(string) + LibXML::XML.default_keep_blanks = false + + if string.blank? + {} + else + LibXML::XML::Parser.string(string.strip).parse.to_hash + end + end + + end +end + +module LibXML + module Conversions + module Document + def to_hash + root.to_hash + end + end + + module Node + CONTENT_ROOT = '__content__' + LIB_XML_LIMIT = 30000000 # Hardcoded LibXML limit + + # Convert XML document to hash + # + # hash:: + # Hash to merge the converted element into. + def to_hash(hash={}) + if text? + raise LibXML::XML::Error if content.length >= LIB_XML_LIMIT + hash[CONTENT_ROOT] = content + else + sub_hash = insert_name_into_hash(hash, name) + attributes_to_hash(sub_hash) + if array? + children_array_to_hash(sub_hash) + elsif yaml? + children_yaml_to_hash(sub_hash) + else + children_to_hash(sub_hash) + end + end + hash + end + + protected + + # Insert name into hash + # + # hash:: + # Hash to merge the converted element into. + # name:: + # name to to merge into hash + def insert_name_into_hash(hash, name) + sub_hash = {} + if hash[name] + if !hash[name].kind_of? Array + hash[name] = [hash[name]] + end + hash[name] << sub_hash + else + hash[name] = sub_hash + end + sub_hash + end + + # Insert children into hash + # + # hash:: + # Hash to merge the children into. + def children_to_hash(hash={}) + each { |child| child.to_hash(hash) } + attributes_to_hash(hash) + hash + end + + # Convert xml attributes to hash + # + # hash:: + # Hash to merge the attributes into + def attributes_to_hash(hash={}) + each_attr { |attr| hash[attr.name] = attr.value } + hash + end + + # Convert array into hash + # + # hash:: + # Hash to merge the array into + def children_array_to_hash(hash={}) + hash[child.name] = map do |child| + returning({}) { |sub_hash| child.children_to_hash(sub_hash) } + end + hash + end + + # Convert yaml into hash + # + # hash:: + # Hash to merge the yaml into + def children_yaml_to_hash(hash = {}) + hash[CONTENT_ROOT] = content unless content.blank? + hash + end + + # Check if child is of type array + def array? + child? && child.next? && child.name == child.next.name + end + + # Check if child is of type yaml + def yaml? + attributes.collect{|x| x.value}.include?('yaml') + end + + end + end +end + +LibXML::XML::Document.send(:include, LibXML::Conversions::Document) +LibXML::XML::Node.send(:include, LibXML::Conversions::Node) diff --git a/vendor/rails/activesupport/lib/active_support/xml_mini/nokogiri.rb b/vendor/rails/activesupport/lib/active_support/xml_mini/nokogiri.rb new file mode 100644 index 00000000..10281584 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/xml_mini/nokogiri.rb @@ -0,0 +1,77 @@ +require 'nokogiri' + +# = XmlMini Nokogiri implementation +module ActiveSupport + module XmlMini_Nokogiri #:nodoc: + extend self + + # Parse an XML Document string into a simple hash using libxml / nokogiri. + # string:: + # XML Document string to parse + def parse(string) + if string.blank? + {} + else + doc = Nokogiri::XML(string) + raise doc.errors.first if doc.errors.length > 0 + doc.to_hash + end + end + + module Conversions + module Document + def to_hash + root.to_hash + end + end + + module Node + CONTENT_ROOT = '__content__' + + # Convert XML document to hash + # + # hash:: + # Hash to merge the converted element into. + def to_hash(hash = {}) + hash[name] ||= attributes_as_hash + + walker = lambda { |memo, parent, child, callback| + next if child.blank? && 'file' != parent['type'] + + if child.text? + (memo[CONTENT_ROOT] ||= '') << child.content + next + end + + name = child.name + + child_hash = child.attributes_as_hash + if memo[name] + memo[name] = [memo[name]].flatten + memo[name] << child_hash + else + memo[name] = child_hash + end + + # Recusively walk children + child.children.each { |c| + callback.call(child_hash, child, c, callback) + } + } + + children.each { |c| walker.call(hash[name], self, c, walker) } + hash + end + + def attributes_as_hash + Hash[*(attribute_nodes.map { |node| + [node.node_name, node.value] + }.flatten)] + end + end + end + + Nokogiri::XML::Document.send(:include, Conversions::Document) + Nokogiri::XML::Node.send(:include, Conversions::Node) + end +end diff --git a/vendor/rails/activesupport/lib/active_support/xml_mini/rexml.rb b/vendor/rails/activesupport/lib/active_support/xml_mini/rexml.rb new file mode 100644 index 00000000..a8fdeca9 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/xml_mini/rexml.rb @@ -0,0 +1,108 @@ +# = XmlMini ReXML implementation +module ActiveSupport + module XmlMini_REXML #:nodoc: + extend self + + CONTENT_KEY = '__content__'.freeze + + # Parse an XML Document string into a simple hash + # + # Same as XmlSimple::xml_in but doesn't shoot itself in the foot, + # and uses the defaults from ActiveSupport + # + # string:: + # XML Document string to parse + def parse(string) + require 'rexml/document' unless defined?(REXML::Document) + doc = REXML::Document.new(string) + merge_element!({}, doc.root) + end + + private + # Convert an XML element and merge into the hash + # + # hash:: + # Hash to merge the converted element into. + # element:: + # XML element to merge into hash + def merge_element!(hash, element) + merge!(hash, element.name, collapse(element)) + end + + # Actually converts an XML document element into a data structure. + # + # element:: + # The document element to be collapsed. + def collapse(element) + hash = get_attributes(element) + + if element.has_elements? + element.each_element {|child| merge_element!(hash, child) } + merge_texts!(hash, element) unless empty_content?(element) + hash + else + merge_texts!(hash, element) + end + end + + # Merge all the texts of an element into the hash + # + # hash:: + # Hash to add the converted emement to. + # element:: + # XML element whose texts are to me merged into the hash + def merge_texts!(hash, element) + unless element.has_text? + hash + else + # must use value to prevent double-escaping + merge!(hash, CONTENT_KEY, element.texts.sum(&:value)) + end + end + + # Adds a new key/value pair to an existing Hash. If the key to be added + # already exists and the existing value associated with key is not + # an Array, it will be wrapped in an Array. Then the new value is + # appended to that Array. + # + # hash:: + # Hash to add key/value pair to. + # key:: + # Key to be added. + # value:: + # Value to be associated with key. + def merge!(hash, key, value) + if hash.has_key?(key) + if hash[key].instance_of?(Array) + hash[key] << value + else + hash[key] = [hash[key], value] + end + elsif value.instance_of?(Array) + hash[key] = [value] + else + hash[key] = value + end + hash + end + + # Converts the attributes array of an XML element into a hash. + # Returns an empty Hash if node has no attributes. + # + # element:: + # XML element to extract attributes from. + def get_attributes(element) + attributes = {} + element.attributes.each { |n,v| attributes[n] = v } + attributes + end + + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def empty_content?(element) + element.texts.join.blank? + end + end +end diff --git a/vendor/rails/activesupport/test/core_ext/class_test.rb b/vendor/rails/activesupport/test/core_ext/class_test.rb index 9c6071d4..48d8a78a 100644 --- a/vendor/rails/activesupport/test/core_ext/class_test.rb +++ b/vendor/rails/activesupport/test/core_ext/class_test.rb @@ -19,19 +19,19 @@ class ClassTest < Test::Unit::TestCase def test_removing_class_in_root_namespace assert A.is_a?(Class) Class.remove_class(A) - assert_raises(NameError) { A.is_a?(Class) } + assert_raise(NameError) { A.is_a?(Class) } end def test_removing_class_in_one_level_namespace assert X::B.is_a?(Class) Class.remove_class(X::B) - assert_raises(NameError) { X::B.is_a?(Class) } + assert_raise(NameError) { X::B.is_a?(Class) } end def test_removing_class_in_two_level_namespace assert Y::Z::C.is_a?(Class) Class.remove_class(Y::Z::C) - assert_raises(NameError) { Y::Z::C.is_a?(Class) } + assert_raise(NameError) { Y::Z::C.is_a?(Class) } end def test_retrieving_subclasses diff --git a/vendor/rails/activesupport/test/core_ext/hash_ext_test.rb b/vendor/rails/activesupport/test/core_ext/hash_ext_test.rb index b63ab309..0edac72f 100644 --- a/vendor/rails/activesupport/test/core_ext/hash_ext_test.rb +++ b/vendor/rails/activesupport/test/core_ext/hash_ext_test.rb @@ -174,6 +174,13 @@ class HashExtTest < Test::Unit::TestCase assert_equal 2, hash['b'] end + def test_indifferent_reverse_merging + hash = HashWithIndifferentAccess.new('some' => 'value', 'other' => 'value') + hash.reverse_merge!(:some => 'noclobber', :another => 'clobber') + assert_equal 'value', hash[:some] + assert_equal 'clobber', hash[:another] + end + def test_indifferent_deleting get_hash = proc{ { :a => 'foo' }.with_indifferent_access } hash = get_hash.call @@ -234,7 +241,7 @@ class HashExtTest < Test::Unit::TestCase { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny) end - assert_raises(ArgumentError, "Unknown key(s): failore") do + assert_raise(ArgumentError, "Unknown key(s): failore") do { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny) end @@ -490,6 +497,15 @@ class HashToXmlTest < Test::Unit::TestCase assert xml.include?(%(
)) end + def test_timezoned_attributes + xml = { + :created_at => Time.utc(1999,2,2), + :local_created_at => Time.utc(1999,2,2).in_time_zone('Eastern Time (US & Canada)') + }.to_xml(@xml_options) + assert_match %r{1999-02-02T00:00:00Z}, xml + assert_match %r{1999-02-01T19:00:00-05:00}, xml + end + def test_single_record_from_xml topic_xml = <<-EOT @@ -884,7 +900,13 @@ class QueryTest < Test::Unit::TestCase end def test_expansion_count_is_limited - assert_raises RuntimeError do + expected = { + 'ActiveSupport::XmlMini_REXML' => 'RuntimeError', + 'ActiveSupport::XmlMini_Nokogiri' => 'Nokogiri::XML::SyntaxError', + 'ActiveSupport::XmlMini_LibXML' => 'LibXML::XML::Error', + }[ActiveSupport::XmlMini.backend.name].constantize + + assert_raise expected do attack_xml = <<-EOT :mutex - assert_raises(ArgumentError) do + assert_raise(ArgumentError) do @target.synchronize :to_s, :with => :mutex end end diff --git a/vendor/rails/activesupport/test/core_ext/module_test.rb b/vendor/rails/activesupport/test/core_ext/module_test.rb index a5d98507..0d3d10f3 100644 --- a/vendor/rails/activesupport/test/core_ext/module_test.rb +++ b/vendor/rails/activesupport/test/core_ext/module_test.rb @@ -92,8 +92,8 @@ class ModuleTest < Test::Unit::TestCase end def test_missing_delegation_target - assert_raises(ArgumentError) { eval($nowhere) } - assert_raises(ArgumentError) { eval($noplace) } + assert_raise(ArgumentError) { eval($nowhere) } + assert_raise(ArgumentError) { eval($noplace) } end def test_delegation_prefix @@ -141,7 +141,7 @@ class ModuleTest < Test::Unit::TestCase def test_delegation_without_allow_nil_and_nil_value david = Someone.new("David") - assert_raises(NoMethodError) { david.street } + assert_raise(NoMethodError) { david.street } end def test_parent @@ -314,7 +314,7 @@ class MethodAliasingTest < Test::Unit::TestCase alias_method_chain :duck, :orange end - assert_raises NoMethodError do + assert_raise NoMethodError do @instance.duck end @@ -330,7 +330,7 @@ class MethodAliasingTest < Test::Unit::TestCase alias_method_chain :duck, :orange end - assert_raises NoMethodError do + assert_raise NoMethodError do @instance.duck end diff --git a/vendor/rails/activesupport/test/core_ext/object_and_class_ext_test.rb b/vendor/rails/activesupport/test/core_ext/object_and_class_ext_test.rb index 0bdbd14f..b6515e05 100644 --- a/vendor/rails/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/vendor/rails/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -109,7 +109,7 @@ end class ObjectTests < Test::Unit::TestCase def test_suppress_re_raises - assert_raises(LoadError) { suppress(ArgumentError) {raise LoadError} } + assert_raise(LoadError) { suppress(ArgumentError) {raise LoadError} } end def test_suppress_supresses suppress(ArgumentError) { raise ArgumentError } @@ -256,7 +256,7 @@ class ObjectTryTest < Test::Unit::TestCase def test_nonexisting_method method = :undefined_method assert !@string.respond_to?(method) - assert_raises(NoMethodError) { @string.try(method) } + assert_raise(NoMethodError) { @string.try(method) } end def test_valid_method diff --git a/vendor/rails/activesupport/test/core_ext/string_ext_test.rb b/vendor/rails/activesupport/test/core_ext/string_ext_test.rb index e232bf83..6c9b7e72 100644 --- a/vendor/rails/activesupport/test/core_ext/string_ext_test.rb +++ b/vendor/rails/activesupport/test/core_ext/string_ext_test.rb @@ -77,6 +77,24 @@ class StringInflectionsTest < Test::Unit::TestCase end end + def test_string_parameterized_normal + StringToParameterized.each do |normal, slugged| + assert_equal(normal.parameterize, slugged) + end + end + + def test_string_parameterized_no_separator + StringToParameterizeWithNoSeparator.each do |normal, slugged| + assert_equal(normal.parameterize(''), slugged) + end + end + + def test_string_parameterized_underscore + StringToParameterizeWithUnderscore.each do |normal, slugged| + assert_equal(normal.parameterize('_'), slugged) + end + end + def test_humanize UnderscoreToHuman.each do |underscore, human| assert_equal(human, underscore.humanize) diff --git a/vendor/rails/activesupport/test/core_ext/time_with_zone_test.rb b/vendor/rails/activesupport/test/core_ext/time_with_zone_test.rb index 1de6a2ac..accfe51e 100644 --- a/vendor/rails/activesupport/test/core_ext/time_with_zone_test.rb +++ b/vendor/rails/activesupport/test/core_ext/time_with_zone_test.rb @@ -751,7 +751,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase def test_use_zone_with_exception_raised Time.zone = 'Alaska' - assert_raises RuntimeError do + assert_raise RuntimeError do Time.use_zone('Hawaii') { raise RuntimeError } end assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone diff --git a/vendor/rails/activesupport/test/dependencies_test.rb b/vendor/rails/activesupport/test/dependencies_test.rb index fe04b91f..a21f0940 100644 --- a/vendor/rails/activesupport/test/dependencies_test.rb +++ b/vendor/rails/activesupport/test/dependencies_test.rb @@ -43,7 +43,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_missing_dependency_raises_missing_source_file - assert_raises(MissingSourceFile) { require_dependency("missing_service") } + assert_raise(MissingSourceFile) { require_dependency("missing_service") } end def test_missing_association_raises_nothing @@ -136,10 +136,10 @@ class DependenciesTest < Test::Unit::TestCase def test_non_existing_const_raises_name_error with_loading 'autoloading_fixtures' do - assert_raises(NameError) { DoesNotExist } - assert_raises(NameError) { NoModule::DoesNotExist } - assert_raises(NameError) { A::DoesNotExist } - assert_raises(NameError) { A::B::DoesNotExist } + assert_raise(NameError) { DoesNotExist } + assert_raise(NameError) { NoModule::DoesNotExist } + assert_raise(NameError) { A::DoesNotExist } + assert_raise(NameError) { A::B::DoesNotExist } end end @@ -206,8 +206,8 @@ class DependenciesTest < Test::Unit::TestCase def failing_test_access_thru_and_upwards_fails with_loading 'autoloading_fixtures' do assert ! defined?(ModuleFolder) - assert_raises(NameError) { ModuleFolder::Object } - assert_raises(NameError) { ModuleFolder::NestedClass::Object } + assert_raise(NameError) { ModuleFolder::Object } + assert_raise(NameError) { ModuleFolder::NestedClass::Object } Object.__send__ :remove_const, :ModuleFolder end end @@ -382,7 +382,7 @@ class DependenciesTest < Test::Unit::TestCase with_loading 'autoloading_fixtures' do require_dependency '././counting_loader' assert_equal 1, $counting_loaded_times - assert_raises(ArgumentError) { ActiveSupport::Dependencies.load_missing_constant Object, :CountingLoader } + assert_raise(ArgumentError) { ActiveSupport::Dependencies.load_missing_constant Object, :CountingLoader } assert_equal 1, $counting_loaded_times end end @@ -421,7 +421,7 @@ class DependenciesTest < Test::Unit::TestCase def test_nested_load_error_isnt_rescued with_loading 'dependencies' do - assert_raises(MissingSourceFile) do + assert_raise(MissingSourceFile) do RequiresNonexistent1 end end @@ -494,7 +494,7 @@ class DependenciesTest < Test::Unit::TestCase def test_unloadable_should_fail_with_anonymous_modules with_loading 'autoloading_fixtures' do m = Module.new - assert_raises(ArgumentError) { m.unloadable } + assert_raise(ArgumentError) { m.unloadable } end end @@ -584,7 +584,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_new_constants_in_with_illegal_module_name_raises_correct_error - assert_raises(NameError) do + assert_raise(NameError) do ActiveSupport::Dependencies.new_constants_in("Illegal-Name") {} end end diff --git a/vendor/rails/activesupport/test/inflector_test.rb b/vendor/rails/activesupport/test/inflector_test.rb index d8c93dc9..6b9fbd31 100644 --- a/vendor/rails/activesupport/test/inflector_test.rb +++ b/vendor/rails/activesupport/test/inflector_test.rb @@ -116,6 +116,12 @@ class InflectorTest < Test::Unit::TestCase end end + def test_parameterize_with_multi_character_separator + StringToParameterized.each do |some_string, parameterized_string| + assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__')) + end + end + def test_classify ClassNameToTableName.each do |class_name, table_name| assert_equal(class_name, ActiveSupport::Inflector.classify(table_name)) @@ -161,13 +167,13 @@ class InflectorTest < Test::Unit::TestCase assert_nothing_raised { assert_equal Ace::Base::Case, ActiveSupport::Inflector.constantize("::Ace::Base::Case") } assert_nothing_raised { assert_equal InflectorTest, ActiveSupport::Inflector.constantize("InflectorTest") } assert_nothing_raised { assert_equal InflectorTest, ActiveSupport::Inflector.constantize("::InflectorTest") } - assert_raises(NameError) { ActiveSupport::Inflector.constantize("UnknownClass") } - assert_raises(NameError) { ActiveSupport::Inflector.constantize("An invalid string") } - assert_raises(NameError) { ActiveSupport::Inflector.constantize("InvalidClass\n") } + assert_raise(NameError) { ActiveSupport::Inflector.constantize("UnknownClass") } + assert_raise(NameError) { ActiveSupport::Inflector.constantize("An invalid string") } + assert_raise(NameError) { ActiveSupport::Inflector.constantize("InvalidClass\n") } end def test_constantize_does_lexical_lookup - assert_raises(NameError) { ActiveSupport::Inflector.constantize("Ace::Base::InflectorTest") } + assert_raise(NameError) { ActiveSupport::Inflector.constantize("Ace::Base::InflectorTest") } end def test_ordinal diff --git a/vendor/rails/activesupport/test/inflector_test_cases.rb b/vendor/rails/activesupport/test/inflector_test_cases.rb index b7ac467c..584cbff3 100644 --- a/vendor/rails/activesupport/test/inflector_test_cases.rb +++ b/vendor/rails/activesupport/test/inflector_test_cases.rb @@ -154,6 +154,22 @@ module InflectorTestCases "Squeeze separators" => "squeeze-separators" } + StringToParameterizeWithNoSeparator = { + "Donald E. Knuth" => "donaldeknuth", + "Random text with *(bad)* characters" => "randomtextwithbadcharacters", + "Trailing bad characters!@#" => "trailingbadcharacters", + "!@#Leading bad characters" => "leadingbadcharacters", + "Squeeze separators" => "squeezeseparators" + } + + StringToParameterizeWithUnderscore = { + "Donald E. Knuth" => "donald_e_knuth", + "Random text with *(bad)* characters" => "random_text_with_bad_characters", + "Trailing bad characters!@#" => "trailing_bad_characters", + "!@#Leading bad characters" => "leading_bad_characters", + "Squeeze separators" => "squeeze_separators" + } + # Ruby 1.9 doesn't do Unicode normalization yet. if RUBY_VERSION >= '1.9' StringToParameterizedAndNormalized = { diff --git a/vendor/rails/activesupport/test/json/decoding_test.rb b/vendor/rails/activesupport/test/json/decoding_test.rb index ebd46a85..8fe40557 100644 --- a/vendor/rails/activesupport/test/json/decoding_test.rb +++ b/vendor/rails/activesupport/test/json/decoding_test.rb @@ -28,7 +28,11 @@ class TestJSONDecoding < Test::Unit::TestCase %(null) => nil, %(true) => true, %(false) => false, - %q("http:\/\/test.host\/posts\/1") => "http://test.host/posts/1" + %q("http:\/\/test.host\/posts\/1") => "http://test.host/posts/1", + %q("\u003cunicode\u0020escape\u003e") => "", + %q("\\\\u0020skip double backslashes") => "\\u0020skip double backslashes", + %q({a: "\u003cbr /\u003e"}) => {'a' => "
"}, + %q({b:["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["","",""]} } TESTS.each do |json, expected| @@ -40,6 +44,6 @@ class TestJSONDecoding < Test::Unit::TestCase end def test_failed_json_decoding - assert_raises(ActiveSupport::JSON::ParseError) { ActiveSupport::JSON.decode(%({: 1})) } + assert_raise(ActiveSupport::JSON::ParseError) { ActiveSupport::JSON.decode(%({: 1})) } end end diff --git a/vendor/rails/activesupport/test/json/encoding_test.rb b/vendor/rails/activesupport/test/json/encoding_test.rb index 2c5b4d03..7d2eedad 100644 --- a/vendor/rails/activesupport/test/json/encoding_test.rb +++ b/vendor/rails/activesupport/test/json/encoding_test.rb @@ -75,7 +75,7 @@ class TestJSONEncoding < Test::Unit::TestCase def test_exception_raised_when_encoding_circular_reference a = [1] a << a - assert_raises(ActiveSupport::JSON::CircularReferenceError) { a.to_json } + assert_raise(ActiveSupport::JSON::CircularReferenceError) { a.to_json } end def test_hash_key_identifiers_are_always_quoted diff --git a/vendor/rails/activesupport/test/memoizable_test.rb b/vendor/rails/activesupport/test/memoizable_test.rb index 069ae27e..39420c5a 100644 --- a/vendor/rails/activesupport/test/memoizable_test.rb +++ b/vendor/rails/activesupport/test/memoizable_test.rb @@ -4,10 +4,12 @@ class MemoizableTest < Test::Unit::TestCase class Person extend ActiveSupport::Memoizable - attr_reader :name_calls, :age_calls + attr_reader :name_calls, :age_calls, :is_developer_calls + def initialize @name_calls = 0 @age_calls = 0 + @is_developer_calls = 0 end def name @@ -31,6 +33,14 @@ class MemoizableTest < Test::Unit::TestCase end memoize :name, :age + + private + + def is_developer? + @is_developer_calls += 1 + "Yes" + end + memoize :is_developer? end class Company @@ -223,4 +233,15 @@ class MemoizableTest < Test::Unit::TestCase company.memoize :name assert_raise(RuntimeError) { company.memoize :name } 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/vendor/rails/activesupport/test/message_encryptor_test.rb b/vendor/rails/activesupport/test/message_encryptor_test.rb index c0b4a465..ed346157 100644 --- a/vendor/rails/activesupport/test/message_encryptor_test.rb +++ b/vendor/rails/activesupport/test/message_encryptor_test.rb @@ -33,7 +33,7 @@ class MessageEncryptorTest < Test::Unit::TestCase private def assert_not_decrypted(value) - assert_raises(ActiveSupport::MessageEncryptor::InvalidMessage) do + assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do @encryptor.decrypt(value) end end diff --git a/vendor/rails/activesupport/test/message_verifier_test.rb b/vendor/rails/activesupport/test/message_verifier_test.rb index 21903088..57c4ce84 100644 --- a/vendor/rails/activesupport/test/message_verifier_test.rb +++ b/vendor/rails/activesupport/test/message_verifier_test.rb @@ -18,7 +18,7 @@ class MessageVerifierTest < Test::Unit::TestCase end def assert_not_verified(message) - assert_raises(ActiveSupport::MessageVerifier::InvalidSignature) do + assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do @verifier.verify(message) end end diff --git a/vendor/rails/activesupport/test/multibyte_chars_test.rb b/vendor/rails/activesupport/test/multibyte_chars_test.rb index 067c4618..661b33cc 100644 --- a/vendor/rails/activesupport/test/multibyte_chars_test.rb +++ b/vendor/rails/activesupport/test/multibyte_chars_test.rb @@ -26,7 +26,7 @@ class MultibyteCharsTest < Test::Unit::TestCase assert_nothing_raised do @chars.__method_for_multibyte_testing end - assert_raises NoMethodError do + assert_raise NoMethodError do @chars.__unknown_method end end @@ -71,7 +71,7 @@ class MultibyteCharsTest < Test::Unit::TestCase end def test_unpack_raises_encoding_error_on_broken_strings - assert_raises(ActiveSupport::Multibyte::EncodingError) do + assert_raise(ActiveSupport::Multibyte::EncodingError) do @proxy_class.u_unpack(BYTE_STRING) end end @@ -123,7 +123,6 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase [:rstrip!, :lstrip!, :strip!, :reverse!, :upcase!, :downcase!, :capitalize!].each do |method| assert_equal @chars.object_id, @chars.send(method).object_id end - assert_equal @chars.object_id, @chars.slice!(1).object_id end def test_overridden_bang_methods_change_wrapped_string @@ -133,10 +132,6 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase proxy.send(method) assert_not_equal original, proxy.to_s end - proxy = chars('Café') - proxy.slice!(3) - assert_equal 'é', proxy.to_s - proxy = chars('òu') proxy.capitalize! assert_equal 'Òu', proxy.to_s @@ -214,8 +209,8 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase end def test_insert_throws_index_error - assert_raises(IndexError) { @chars.insert(-12, 'わ')} - assert_raises(IndexError) { @chars.insert(12, 'わ') } + assert_raise(IndexError) { @chars.insert(-12, 'わ')} + assert_raise(IndexError) { @chars.insert(12, 'わ') } end def test_should_know_if_one_includes_the_other @@ -227,7 +222,7 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase end def test_include_raises_type_error_when_nil_is_passed - assert_raises(TypeError) do + assert_raise(TypeError) do @chars.include?(nil) end end @@ -262,22 +257,22 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase def test_indexed_insert_should_raise_on_index_overflow before = @chars.to_s - assert_raises(IndexError) { @chars[10] = 'a' } - assert_raises(IndexError) { @chars[10, 4] = 'a' } - assert_raises(IndexError) { @chars[/ii/] = 'a' } - assert_raises(IndexError) { @chars[/()/, 10] = 'a' } + assert_raise(IndexError) { @chars[10] = 'a' } + assert_raise(IndexError) { @chars[10, 4] = 'a' } + assert_raise(IndexError) { @chars[/ii/] = 'a' } + assert_raise(IndexError) { @chars[/()/, 10] = 'a' } assert_equal before, @chars end def test_indexed_insert_should_raise_on_range_overflow before = @chars.to_s - assert_raises(RangeError) { @chars[10..12] = 'a' } + assert_raise(RangeError) { @chars[10..12] = 'a' } assert_equal before, @chars end def test_rjust_should_raise_argument_errors_on_bad_arguments - assert_raises(ArgumentError) { @chars.rjust(10, '') } - assert_raises(ArgumentError) { @chars.rjust } + assert_raise(ArgumentError) { @chars.rjust(10, '') } + assert_raise(ArgumentError) { @chars.rjust } end def test_rjust_should_count_characters_instead_of_bytes @@ -294,8 +289,8 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase end def test_ljust_should_raise_argument_errors_on_bad_arguments - assert_raises(ArgumentError) { @chars.ljust(10, '') } - assert_raises(ArgumentError) { @chars.ljust } + assert_raise(ArgumentError) { @chars.ljust(10, '') } + assert_raise(ArgumentError) { @chars.ljust } end def test_ljust_should_count_characters_instead_of_bytes @@ -312,8 +307,8 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase end def test_center_should_raise_argument_errors_on_bad_arguments - assert_raises(ArgumentError) { @chars.center(10, '') } - assert_raises(ArgumentError) { @chars.center } + assert_raise(ArgumentError) { @chars.center(10, '') } + assert_raise(ArgumentError) { @chars.center } end def test_center_should_count_charactes_instead_of_bytes @@ -391,6 +386,15 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase assert_equal nil, @chars.slice(7..6) end + def test_slice_bang_returns_sliced_out_substring + assert_equal 'にち', @chars.slice!(1..2) + end + + def test_slice_bang_removes_the_slice_from_the_receiver + @chars.slice!(1..2) + assert_equal 'こわ', @chars + end + def test_slice_should_throw_exceptions_on_invalid_arguments assert_raise(TypeError) { @chars.slice(2..3, 1) } assert_raise(TypeError) { @chars.slice(1, 2..3) } @@ -436,7 +440,7 @@ class MultibyteCharsExtrasTest < Test::Unit::TestCase if RUBY_VERSION >= '1.9' def test_tidy_bytes_is_broken_on_1_9_0 - assert_raises(ArgumentError) do + assert_raise(ArgumentError) do assert_equal_codepoints [0xfffd].pack('U'), chars("\xef\xbf\xbd").tidy_bytes end end diff --git a/vendor/rails/activesupport/test/string_inquirer_test.rb b/vendor/rails/activesupport/test/string_inquirer_test.rb index dda7850e..7f11f667 100644 --- a/vendor/rails/activesupport/test/string_inquirer_test.rb +++ b/vendor/rails/activesupport/test/string_inquirer_test.rb @@ -10,6 +10,6 @@ class StringInquirerTest < Test::Unit::TestCase end def test_missing_question_mark - assert_raises(NoMethodError) { ActiveSupport::StringInquirer.new("production").production } + assert_raise(NoMethodError) { ActiveSupport::StringInquirer.new("production").production } end end diff --git a/vendor/rails/activesupport/test/time_zone_test.rb b/vendor/rails/activesupport/test/time_zone_test.rb index 6dec6a8f..b01f6246 100644 --- a/vendor/rails/activesupport/test/time_zone_test.rb +++ b/vendor/rails/activesupport/test/time_zone_test.rb @@ -244,7 +244,7 @@ class TimeZoneTest < Test::Unit::TestCase assert_nil ActiveSupport::TimeZone["bogus"] assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone["Central Time (US & Canada)"] assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[8] - assert_raises(ArgumentError) { ActiveSupport::TimeZone[false] } + assert_raise(ArgumentError) { ActiveSupport::TimeZone[false] } end def test_new diff --git a/vendor/rails/activesupport/test/xml_mini/nokogiri_engine_test.rb b/vendor/rails/activesupport/test/xml_mini/nokogiri_engine_test.rb new file mode 100644 index 00000000..e5174a0b --- /dev/null +++ b/vendor/rails/activesupport/test/xml_mini/nokogiri_engine_test.rb @@ -0,0 +1,157 @@ +require 'abstract_unit' +require 'active_support/xml_mini' + +begin + gem 'nokogiri', '>= 1.1.1' +rescue Gem::LoadError + # Skip nokogiri tests +else + +require 'nokogiri' + +class NokogiriEngineTest < Test::Unit::TestCase + include ActiveSupport + + def setup + @default_backend = XmlMini.backend + XmlMini.backend = 'Nokogiri' + end + + def teardown + XmlMini.backend = @default_backend + end + + def test_file_from_xml + hash = Hash.from_xml(<<-eoxml) + + + + + eoxml + assert hash.has_key?('blog') + assert hash['blog'].has_key?('logo') + + file = hash['blog']['logo'] + assert_equal 'logo.png', file.original_filename + assert_equal 'image/png', file.content_type + end + + def test_exception_thrown_on_expansion_attack + assert_raise Nokogiri::XML::SyntaxError do + attack_xml = <<-EOT + + + + + + + + + ]> + + &a; + + EOT + Hash.from_xml(attack_xml) + end + end + + def test_setting_nokogiri_as_backend + XmlMini.backend = 'Nokogiri' + assert_equal XmlMini_Nokogiri, XmlMini.backend + end + + def test_blank_returns_empty_hash + assert_equal({}, XmlMini.parse(nil)) + assert_equal({}, XmlMini.parse('')) + end + + def test_array_type_makes_an_array + assert_equal_rexml(<<-eoxml) + + + a post + another post + + + eoxml + end + + def test_one_node_document_as_hash + assert_equal_rexml(<<-eoxml) + + eoxml + end + + def test_one_node_with_attributes_document_as_hash + assert_equal_rexml(<<-eoxml) + + eoxml + end + + def test_products_node_with_book_node_as_hash + assert_equal_rexml(<<-eoxml) + + + + eoxml + end + + def test_products_node_with_two_book_nodes_as_hash + assert_equal_rexml(<<-eoxml) + + + + + eoxml + end + + def test_single_node_with_content_as_hash + assert_equal_rexml(<<-eoxml) + + hello world + + eoxml + end + + def test_children_with_children + assert_equal_rexml(<<-eoxml) + + + + + + eoxml + end + + def test_children_with_text + assert_equal_rexml(<<-eoxml) + + + hello everyone + + + eoxml + end + + def test_children_with_non_adjacent_text + assert_equal_rexml(<<-eoxml) + + good + + hello everyone + + morning + + eoxml + end + + private + def assert_equal_rexml(xml) + hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + assert_equal(hash, XmlMini.parse(xml)) + end +end + +end diff --git a/vendor/rails/activesupport/test/xml_mini/rexml_engine_test.rb b/vendor/rails/activesupport/test/xml_mini/rexml_engine_test.rb new file mode 100644 index 00000000..a412d8ca --- /dev/null +++ b/vendor/rails/activesupport/test/xml_mini/rexml_engine_test.rb @@ -0,0 +1,15 @@ +require 'abstract_unit' +require 'active_support/xml_mini' + +class REXMLEngineTest < Test::Unit::TestCase + include ActiveSupport + + def test_default_is_rexml + assert_equal XmlMini_REXML, XmlMini.backend + end + + def test_set_rexml_as_backend + XmlMini.backend = 'REXML' + assert_equal XmlMini_REXML, XmlMini.backend + end +end diff --git a/vendor/rails/railties/CHANGELOG b/vendor/rails/railties/CHANGELOG index de506dfb..e8e8434a 100644 --- a/vendor/rails/railties/CHANGELOG +++ b/vendor/rails/railties/CHANGELOG @@ -1,10 +1,7 @@ -*2.3.1 [RC2] (March 5, 2009)* +*2.3.2 [Final] (March 15, 2009)* * Allow metal to live in plugins #2045 [Matthew Rudy] - -*2.3.0 [RC1] (February 1st, 2009)* - * Added metal [Josh Peek] * Remove script/performance/request in favour of the performance integration tests. [Pratik Naik] diff --git a/vendor/rails/railties/Rakefile b/vendor/rails/railties/Rakefile index 4b524f1c..6c0fc226 100644 --- a/vendor/rails/railties/Rakefile +++ b/vendor/rails/railties/Rakefile @@ -311,11 +311,11 @@ spec = Gem::Specification.new do |s| EOF s.add_dependency('rake', '>= 0.8.3') - s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD) - s.add_dependency('activerecord', '= 2.3.1' + PKG_BUILD) - s.add_dependency('actionpack', '= 2.3.1' + PKG_BUILD) - s.add_dependency('actionmailer', '= 2.3.1' + PKG_BUILD) - s.add_dependency('activeresource', '= 2.3.1' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD) + s.add_dependency('activerecord', '= 2.3.2' + PKG_BUILD) + s.add_dependency('actionpack', '= 2.3.2' + PKG_BUILD) + s.add_dependency('actionmailer', '= 2.3.2' + PKG_BUILD) + s.add_dependency('activeresource', '= 2.3.2' + PKG_BUILD) s.rdoc_options << '--exclude' << '.' s.has_rdoc = false diff --git a/vendor/rails/railties/builtin/rails_info/rails/info.rb b/vendor/rails/railties/builtin/rails_info/rails/info.rb index 7b6f09ac..a20d9bfe 100644 --- a/vendor/rails/railties/builtin/rails_info/rails/info.rb +++ b/vendor/rails/railties/builtin/rails_info/rails/info.rb @@ -85,6 +85,10 @@ module Rails Gem::RubyGemsVersion end + property 'Rack version' do + ::Rack.release + end + # The Rails version. property 'Rails version' do Rails::VERSION::STRING diff --git a/vendor/rails/railties/environments/boot.rb b/vendor/rails/railties/environments/boot.rb index 0a516880..0ad0f787 100644 --- a/vendor/rails/railties/environments/boot.rb +++ b/vendor/rails/railties/environments/boot.rb @@ -44,6 +44,7 @@ module Rails def load_initializer require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" Rails::Initializer.run(:install_gem_spec_stubs) + Rails::GemDependency.add_frozen_gem_path end end diff --git a/vendor/rails/railties/guides/files/stylesheets/main.css b/vendor/rails/railties/guides/files/stylesheets/main.css index 5061b130..d377628d 100644 --- a/vendor/rails/railties/guides/files/stylesheets/main.css +++ b/vendor/rails/railties/guides/files/stylesheets/main.css @@ -337,7 +337,7 @@ h6 { margin-top: 0.25em; } -#mainCol dd.warning, #subCol dd.warning { +#mainCol div.warning, #subCol dd.warning { background: #f9d9d8 url(../../images/tab_red.gif) no-repeat left top; border: none; padding: 1.25em 1.25em 1.25em 48px; @@ -426,4 +426,16 @@ code { .clearfix {display: inline-block;} * html .clearfix {height: 1%;} .clearfix {display: block;} -.clear { clear:both; } \ No newline at end of file +.clear { clear:both; } + +/* Same bottom margin for special boxes than for regular paragraphs, this way +intermediate whitespace looks uniform. */ +div.code_container, div.important, div.caution, div.warning, div.note, div.info { + margin-bottom: 1.5em; +} + +/* Remove bottom margin of paragraphs in special boxes, otherwise they get a +spurious blank area below with the box background. */ +div.important p, div.caution p, div.warning p, div.note p, div.info p { + margin-bottom: 0px; +} diff --git a/vendor/rails/railties/guides/images/error_messages.png b/vendor/rails/railties/guides/images/error_messages.png index 32de1cac21a67964510d81fd1ea9fbf5164a8e79..428892194a61755fa1d12d97f5e69c68dcd3ff28 100644 GIT binary patch literal 14645 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 literal 8440 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 T<^DNvUr19;N44UHZN&cqQO0-R diff --git a/vendor/rails/railties/guides/images/fxn.jpg b/vendor/rails/railties/guides/images/fxn.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b661a0e402143b89f498aece13ea21acaa9085b3 GIT binary patch literal 17868 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 R^dGc>)0Ow$+w1zD|Jlm+CVKz? literal 0 HcmV?d00001 diff --git a/vendor/rails/railties/guides/images/i18n/demo_localized_pirate.png b/vendor/rails/railties/guides/images/i18n/demo_localized_pirate.png index 22b93416a0f8a615a1abe1b68751816568f6c7a4..91347095739cdd7028f6652080a5395a460c6139 100644 GIT binary patch literal 15027 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

SOD%&_!Zh3X(qF`~5fc^kk6;SPIBzNc(v-G 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 B%Ygs@ literal 36500 zcmb@tRa9JC)36Ik2o~J6gS)$RC%9X1PjGi@Xd3rGaCevB?oNQ^7ydRRr1yU%Pq5tvI5-S- zOEEEJ88NYs%1-uXmNuqva31hKwLJOb4c9@nM3JWDi{EMDd93qlPQ7kIkK_jkjZuT> zwTBZ?VnmRGutiJM=yQ}s7l(CJMEitql{;bJwOeq-ZPu;cJS1!UKK;~b>^}1`{m|=e z7wv{3EzTyKFPth;|B^By%%cxE#0Qbx7vbm)ga@7ze?=0jf(n_zJZJLLsDEPE0^dc7g|UR?iKq*@gov|=Jn9* z_4$rE(3HMmt_8CL{ww}E*^2bvK1~6^y}}U7mQ%QFV>(YPX}D~HkL8=YWJk!Z(@$Ve z1h4m>_{rd!BtL!nXaZP9Jy*>%LCKFO82y~yz^aUvV2lM~$2O1JiCkD~J31EIPJkAh!o&5BvI|m^dtvCQ_0s$xZzBjXrW^)nAP8?y1usPVO zP)_u5YMDKmed(LD{QmE2W-%@5G|pBwDJ&R$;~6WZ5)L4ET|ZkvTABa-t+twBxhbDP zuA;1uQZu(AtsFWFppd=9qCzdNCs$V4%hz1%q-_43U55IIdjh}BCQQD3Tv>a(Kbm}Znl6L+&4xz{1@?n|%nNW@xN zFCwgUUMV5lQE%Na=9kP97W=0bfR?l(66F`Y02VQBF}z;t-XKK>i7IhUaf-YKMb<3h z=#!{sfUcV$3#;(japb;s!MhjdQ0L3AXBn0uQ-%^)FDt2By&S4s)-Uc(EoWq}6psP{ zFD}hzt4{`QKF{@I{_a6}62|>hH1@Ks!bG`$GrSr+>Z@$lik9d0JYFAfe_U@SH6%sbfyO+rS5EHKE>m`#Z_p>I7X;RL z&T=nJ_1!O+8p(GJTGrelrj&NplfpmB_3SbLO|)$AP;{*@Uh{l86ultWXM1Zn z-66nQ$~**ZyoO*_C+V8`%NJ=LK@qNix$y5jnd9gF&H=i%nn+4yr))lAe)8QiU)A+uf0ZX^ctivS z?s%4*dL8E;tG(40 zv(CfZj-YK+$Vx>^8P~PZ`ig$36|wwlv2@XLnM6QKNRZso@xqOkl%^%HDVPk^U1*;i zhIN{mZr)0K>Z{7U5YAqK!WH^aCq=}P;JJi46Uf- z(4z>j!^NMH^1VI6Z{^RH-}%El=I#v52gcoKXEH_f|gv2&N0hwYY+-AI#pjy`K(tkNg(- zU1321tPuCR?zd0Mysk3yE=#BOEE#&EMq}ZUaBaJhxiy=`>rl=T$JH+iy1Ho+SZ*FQ zfvaY`^?J3n-OC9hdDnJPt?1wJv--zFCBgDeL=pAv4+`N8%P3N z(sX+yG;~^29?aCd@t`Y%WLti@yRNWUB-#AWxX_ucAy`p(p0TmsH;nW@pmr=i(sd`= z*Q2m^gU*6s!7>d)Ck)-NZPxrk1GzE_&6CaBN0YyZsXEMtRJvo3z4n3a>&@`2f@)137h6%Q^q`ii*~ zuSJ+O#&HHv zVWT^78`hhR+lJWEc=0$#I4F4dn0A8*p-7=F;gaD)VG3b}tH&R^StprN7)(1u?8gV& z`SJryxJ|W9p(D@nhN{V{d^!B3+{G(s$HR|S^^f)&s57+~`DDWG^omrHTq=j9ZUu^I zCqnIbZ_&jNsC2ib<0z*UYqRhv6B9M6Lk`OgmxUV?s<;~0O+m*Kd&y3Hy)w(%2lPd# z!$AkKuDwkWU<;22u&Jl}J?BFT7H1USJqFIa%o{%)ldNo);ed@@Wrn?CCVmcb1|mF< zi_hFRb)BhO3tIu>8{@Zy;KHBgCVIHzR!uf81)xD5cR@CGX#K~sUmfDb zE}P)x@40DV>Yp3ats)QNPf%vRkkF=)^T0$fy-jB9uJv**mq(aHydFv)3f@NqiwDaA z38R@5N))$Y{P-u3Z5`Fe?)sHB%1*G=&j-zW@JaBf{hxI{UK+ki`{OmRxa%?y^r4&P zjC=sKHlUzaFsD|GOwn&WP>I3;iJ(ut&x;y#LC*;g2*#GW&t+7RbFp$M+RM!5u+Vh@ zf>RB`fT>Y>DEBbBUT;!Kduxamr z5y7JH;#v(!?KSl&(8>J-!O-oD5Qu!>K+7+^UouC8AZg_!g z&~P(jN-4q2*fHpPv?_+sr{EK2|M#Ec%JIrFO1~8AR?e4o8cCZ|TiTe3a*T3#ee1Ab zFfsMyUwcSYsJH##a`KDch}4XkKa%@^(b~L)v>(mIP;MtnupENo+RtCgY4bR zIpig{j|$!=Ke;Pn=O9~g*c-`<9{8K;X)DYeOIO8;u2*qZsXTuZ6}d{~>=(8eO_0fiRX7ACU0k z;qo*WKLcCR&bvRUw2GKqpH6zK=}<;vt$XN1f(R|M&E;!tY-!lGe^1_>Z-iWh;eh8s z>HRJzQd;6SmBJ4`6?uB>1D;i%7HbP!K}?Z%5SZ(Prccx+VjjWv#{tsmvHYPcU+sYA zjQ;dpkungzQSGAI!uv%TQi~{6MaY~yOs2m&(?sBor1TEV@!1F*W0NUIaVT~P%u38$ zSL+ssq!P?|t-1$P9Y6&>#j7c3AhqDMf$}-z8QNGyfi(~LM3lLTZn!)!kdIEFi7xgdC1?m%eUr`j{cYIzyY#goGVzI;Y2 zM!Yg&JBHPYp9v)+og*3mSNodD{F0WG>Y#AQEi-Q_@#@di=*aK%5weYnuz5^=pOs{1unA1ukRDen7pQa>;3C3 z%Ew?)fkn5kA#3u~7^rf^g_}f9p?E9=JHeUP8|);I;sMcp%()OFqbJ1ewv}sn3TC)- zlo+2Dd`vU{suYa=F5;daWXao(?l??HwV&>q_=d**U_kxlSH>JIIfr&I$x?+e0x#PF zu3v{&zF*b4-sL?;Vq4iFesz>CTnA0W(<)uo|JnU{Yi{E}9cJMjockyEjxLg4Z@98iuU^_5F{1;P{FE#Ckb-v#8+Ko07wsF7U&Z9`?`)P)m*>tA zvnVorjRW~nOsogNbZ3G5?w=k;=+zj1Gd{g6FWpG5+&pOZ;#xh^)f!uxy9kNA+dzAAZPBNflV~gM$N!0jK|KlH*c;mjD_4oG>9fXdr_S-qt$1%DU`kB1$t!p zUV!0T-VR~m`d1nvDUv)mZ?Xh%(RvOKZQ$bJ-ULA5`C_b)TfV_v{5@wQJ4kCg!@;5A z{L|p!Qqu|H;6B31h<{e|fIosDd#m>^zQl>ZV@qqHJr??6GSB4JQ+_U^Pf+e}kzi7P zTfnHSi&^T8Z^Lr+$dq00i|T`-5u}r+9zRUEfTC`Lj8gm&UPkN9yP6+?)GM!#nO^HA zobF@Ie5g(Az4jMlZOgxfVJnX-0yEN+YZ)B&~PMZ}8zGeE(lPa^tHmww-kFZAJj!G6g@D& zG&s!5^1l;qSo?l54y&y`{~o+iMp^g}sS7Z~`S?8y_SR%up*Xmx_HOZVu*0Xv>t}M? zSp2+_acC8=`ud!QEE;@B?0hlrUga=g#R!1dZSKq(+pPbqjUw3_G>SHdZ>hsYlT4u5 zJ{(0KJ^)`kvtYdbQ+eJ0s_SRZ`zA+vdi11Fa}sG#>KdaJQYy`;X291fV?ULGPgs%F zR|9&hyUrhGZn@3kbMvHPh;?nfJU^at`M?pAJb%u2pEkxCmnAb;Z3iAA8D{nQIx^5E zko^997-lwo4K^ez%HbW`#4C&Xy8N$g9jvgBB+LwM9u`)-SFhTv*mT>KEoG-BozJPY ziw-^gTNF=gF$Sfa#29EnaZzIO=dz_g6jGqlQUr)30Pl4DY|c^^AAC^be8YL*WcZZ6Sg@I>0ho@mRm+=%rkVZJZ1|U zdau72+bsWUrxh%AtkfZ)_$<5IgF{|&rTL(PGUpkHy6!JGQv*`N|& z`TXP7VIe_l!hduDV>(c=>Q~;Hx|IUcc?U~;Hm?+n!&sRY38x$*X0NRbZ7<(7k4Km> z@W$kqPCBMV3>6O60uHZ6M#}l{;wpEz?+*pzDrJfWW!87Br@}f}6*_1$$mt+|Wppns z-1n(2;Pk71WOs*Ce>?0uPVxPfZB@$s|2`-@%Cz{~1y6s8U?Xod4R1`T;veh^g(n38 zwKQv88fHzpfNmDW+17f2j3HXVn3k-i+Y6QFrqQ5hA~l|yJ%Ofw4`-RZ} zyu4+8ehG#gz#`TfVdMKkP(thW=dEZsgg*T#mRZgUBilo|yZ zuKJ%#;*}!~5H(OcnPL`1SWaw>`iii!!z!*JZ6~ahx1J>eNt5oyN^w#KeP7q|-#9Nq zgr^WAQaC*X&$8Oa95LA@S}n6gCq<_!MY|tHtkECJ@wRm8g5nL1XiE+~w#uikr;>tX z4D45b5EzbS@<|NXt+_60OpX ztqhlP=bv3$?@D5qKg2%x#t|1SFBq~MjR378t!`_=vXY{cMh70T&U${0n4Q<&alA2r zg(i(=f)w0ekP=%=p00BbntqZ~Rqw|-hgPoP*UTMn(M`pFu_>KdE(9J%_J5a_AEPy# z4BO#vXMyO~RAN#rtF^9~8?KJQdES?BtRzx;QfFT0;$%7G69b|;PmH%of?GE%{!YcS z<7zz)0KF;NzV0>NlW;k>%QZH%JXE#G)eKXs56dXfO?#g8IFA!%Zf@FlP20A4_>zUA zc8%7^;=^1pdikS6S{*i*=%Jtbq&?WbQ+Cii`Y{}Ed#G#E)<2#Jx`xcoJp6BZQ9+Iv zhdW7g`G!g}d#7wW%j#)r6Zaj`6dJ9$?8yBb#>;>v*vN;e&StBAvEz}%D5yAUTc7Gu z+SxKmJA3f$udSJ=lxJH+lF1HhfyiKdHQ3 z-4d^SJcOJ($F+2>xkuo%|CRNRD;&Il*I5*={>lhId?kN7H1hv{9K_HnUhUkbBM+IZ z-eEi)wKDWxC_L;R*iIfuBIK#86A0e!m$I!@VA;w$@L$@*wo%S5RHpkyyI;05EI28r zn}AIR#3qpn7B6Kf8K}X*`;xw=UeZ9rc9@^+@~w92-~2@!?JEn8RdAuyX0wem$=G8m z))&1dRJW@&p!wosXr3W$B`C3~7nXrGWyVk-)09K;j_K`TEZu;hdS#;mBy5rfCYQZ* zbJBVWwC@YW+ShM!Axi!6VUx)_O*NSMC zzEu8)Y6(#dQguzW2eu~md(0$BIq0@?quRPmw3M#yoi57p-) zE^7r30Du}uNFXR??qD$f-+6*RnV3*-ZEYzV79Y?_Laa*TjW;Qs-_hTB>Dzy|4(xRK`S~Ac=rePN zy>sFKaph;IjjQ2($mY%WJko{$|6Sx_iHFjihAUIYT$~|+{9{_0MAIVWq#y_oz{tep zX!7dw1iR*|dW}<3R#EHQ{>aLJOg1J$u-*)&Ruy#lBrpHLLh5Lt?WI@%d9&km3vLHId!X|?!DqNXy8P@JuKA?tbOpKKAY?5cJ= z#Veyec&wZNuMEj&i?v+=!ZO^~v}LC1*#*wq7g@?Qw%5mjA^0wk4%^lSi|Oh-F};w> z3x_i!c-eiN@msrAytk}mCwjfNl-fi0`>E;+H%YHzwM|-eLS=7tT#rO+qq-*B*c=_! zfuwAepVmpB;m%WRXhN`5v{R$~v~kJ~i4UXIXiN3B(FX@sr2I`7A(zqF7alxzK%^4SJQSM&y-t!lZKaI>V zO992Jo#9RW1xI%D=Z8W;r7G_c<+>5eH}6GD`f!-BvDu<(aID=Ay>}={Pv|_?T^p0JfpmaU4nZBpwb`!Bg>mu z9~;}@^E~U_X0b|>)d8-zec!J1Pkui$1Vj}T9sgsJm+#5(xOEhO4(*eK%gg$*D&u|8 zD;p9elX)^w(~aztQI81!=`bMIOO{74T{v>sSn9k#_|`8Ux9(ty$7HOYM04}iKsPio zW6N=EB9oBof>^$7Q&YP03_Dd*fH9Tqvx*EduXh#v5Q|RUi1wPm>L0_h%C036yWU82 zv6w}=e%JQ;mlJ-z0rTYg5!W9`XT%l959&9JkT2QrJv9K|-0R^vNR^R@xqU=)r%$6Xt1sEPsK;3cr@we_~>?%12y*co|@ z#D#o`R(xitd$8%C3o%)2{QFhM1}W+4752&VKJ{;yr^;x=UO4c%<>O}GKEERH$kUnl zBoi;fn)7i+Th4f_|3@`m>-m%YYh-NgxKt7T$tf1aCH;c#dLj7uJ$?{1DA zSyhFaU@m0k0~=zOr+yA$?ruXo2dR=gPJHE(b`gGrbDbH6x{wbfYcF&_=BOZn-bzrr#zpXi-AuJFSp;>svW%j*YQ()$GAnEADlFbx7U2b-idl!xB(r2 zXj=d(TuC$l@}`kCk>B1<+1Y|~QnqdcPkWh^^1oh3rk#zDj8xM<+PTc3pRc7iR+U;1`Vgsn7o#h6+>ab|n7vrp=&Yfu{Fr(l@UnqknK@oC{D)y#_+ z#-EN&9#f~=VR!{l+a5=7M*OIugPk9fq~H=T3qD8xGS?3S0+l?a4fmJ z0c>~xQ(*>kfBs79K49{U&;A&t&h8{H!<2DLXMl>9Bi3N&QRRBqi~CVpTzxkVh&>b& zCT)-PovQ5C*H}j%H1}h)Qs??bH#GcmvlVm@-e)o_b1X*ZzTJsnt>RhphDHTJS~}|U z;~$pEBPmC%*WZc`zZLTAoJWb4lt(eM&rAZdfOM;uR8r|egDvp@Ik(eY)f%l7vqqiITKW8hUY@-4CRkDEpu%Xi4NqhkZ4E?8N^QPQ z@f(IvFLFXIz!%oT;4K{U?c@7p$9@quJ~b% z4SEuovulUvL;Hwb#z3?1G%OfN1jV4-*K+CDA|M2)_-$?DS-8($6LMX89z{jvZ+i$v z;i%wcwE@9a@lkPYpJ(K+)p)kX2BrlBiuq$h{9}VBS~huHOaEA1a=sC*l21Rdq|7fl z&>jSO8wHgWTQXzYa|f5%Aa$dB9)2e=oIJ%RbMv(wvsfm444!r7t>R>>+gGDnvpspC zkvSJ=9Z`&p{3e#T&sh#vR`P|@nbPNeNeN1SgQ}x6DGmcq91L5rTjwu7CZgQpe`1Fk zZDVG9V!Ie-OW&8GxiH=u#ad-XiE*5~L%;t+zIKx0JU%%-vHjFwu^tAD8I+_*h2f}2 z`InCGi}b;E?6F2#8JAf2BBX9l8~QV!vpoN};P2fJLZj*gV?r?2z0om%)roDbn`0s5 zU9*<9F|Ae<`dt}+3 z3i4Ykz1#taE&vE!2w*F@j~++1(Z9w%No!=r7vNUJrG&Lh5@m=q zE&LfVTF^QoNA9Ex`)Elsd~J2_{cPvUXMjl|)yV`r?lcE|)L|5&1p(;rS-kXftuLv|*3iguWvH5pr zF}VAL0#;a+s|=Id`F(%IUojZQ&sS{>iL1L8| zDO`2(AadpK!czqa6%m=NHlYc+3uD~vdNJ8B28mWhTg>%DB;IDCZ>fV6oIv83_;pXO z3aj9~a4m6~K1%-qtND@-gR%7XsMZ#*q`9``gKI7@Qgt=+~K*WuJK|mK`fL(7^rf%qX7Ews^ZDl_KwDtWeYX|=P zm=~%5-+K6C<(^-1qWH_RPk`;VPe>ifsldFb$A zJTfin(azeh=+%u()q-xF?b$3FlFPe}b7ymG&HvhR=<9UMX*k6Clo7f~Fs(Z^#!^*V zP=C2Nl5}%_=Y4()+Uh9c+k!b8c11t-Z}$?wzCP^evfoz|u*a`GExcIQ81n2#d+b~| zt8_pKhSMaz*a(z5;J@6hFcB`md$@1%+^s$XHgY?^=dUlvla(LEwsXkloK|&uKfilI znBP!h8<)J@)As-mv$1bJU^DBy;MDaSw2AK!^X)5X36u(56w!#i`o+sP1#h^!&>= z_aQ2>aT~NrR9TlHQ!;y`Dym$3gP*8h8l?)ScbBrZU&_34A21>G)V(Dlit$-&JQLF1 ztm0FST29jUW*crib_zZ7Oi88NxF2P0*8Ts zu|?+j6-Bm$us%7h(|4L0#n7nF!VpZODb~sgQEgMa7zLivTt!Mh5aEwerXnb(0klk| zTgrIBrR{RC3`^UCbV#i7&76;a9u1L2#uV0b2kMdCI-2>UX0l=?eMqI3{n_e%MY~sy zSO`))9zh=GRLFKd$?J^J-Tq<>eMMwXB|&8W(s@KoE9foDGL5mx-0G)&3R3=3z4wI~ z>%+Ic)+bgnhe9gmxf0E3mPNvu2XbxH8O;$js@Is83ChjFA| ze`(p_d&9CP8_1Q~=7;^&^DAyETgWfGyVdCvabAzJ5Pp}yuuNJef3 z6E(nYInvgpvqP!U;4Fb$VhH)+AOU`)bkA&Wi3h7-FkFR9%S~<2uL)Qtf_@4j!0R-O zTvvFe(s>$JnrsbvRsUTZ9a&DZvw-{hd_mvxo9r`yzBxVtD{A&!k%Ue*gNK}v!F>!s zUg+)}=xldSHe3Z|sb|e$;&SP@_k4NhR&E$|`f@-do~S-u56@t_zz#wk?_UdO%xs3I z>-;@iVsy?(O7yGj*|@*i)5 zRVd869frVlGH?x{$XsaSc;r>c^zr_|W9?y^G8=j!rAu(tqYl1aF|%YWH6&|&oz<4w zU5pWL!8t6rFew=I!S0QAA)d)1AG z(V|h`tsMK9fp+F$b>Q+aOn&cR+a0<cu%GOWi`|{6kiKLbvo^jG4}8^dELqb%?+F zB*9gBr-w}Ctdc&`5V&;vwIe=JZklb&%c_BMqW?6e=mTa-75K&C=u)DnlsecSa`#xrYT zF6)P2H;hDtaDXP5oU>R!GjydnU7=NT?i>ty{RSFlm3L&{e&x!0rrA+e-TYB;`9`D~ zaJU9v@nC5kKZJ*%sMbwG!{dL_vGOnrs5gF@HyOTlhb4a5x$M^_C zJ9}`)$$)j_zBe*?vPe2cIiWQ*bgZHfgRFnhl8NL%&#ojrAkx08Q$GSl@D}5}Q%ja% z;k;UL^St~FFR{OGFOJT~&c&)srT4)0Ag^NW+Bm!^JV1kE48PsTP~SONH`@3$8=JNJ z0uGE^VBD&?8&vGJUt3;ca4YL6Dr0H6(CUW9s9P^CjAQM@xkm@Y{0w5>#07fL*{+gFBF2O@0hpg7RMcCh;L>`9humezW}D1f{Qe0Pau)i55X?#)9 zT$j#zJR-jD-rm?Lm-muOmm0$~ezmHw?C{w2*@3sj4_?;&r|U1*0o1ZHD)7|J$ULC0 zzIHE%!hF>4(bJFDd{fwtP@NnWjW1Mnt1KW`e;ci;?bJmylw!Wiq1FHh9(;w+pil3Z zt$+b1&~2Ni-NI7`jloLv#3;!}F$^FXuy1?95-%-OPMW-cOsKLI4MIn#74Qw5XOF{g z5HpAoX9pL%y%%5*LEaP#K85P|c8e{2i2xo=PA|U0DJ<#}5?Hz}979BSXK!>&KL8v! zuQ0Gs6a<2I@0ZV&o(Mro`Rk9Ys2{|^eXc!$k7n7XE)--;uVBS-N6M>o%R9$n*2`_@ zQ)g>ONS2u}7b7s9bS2XCPHolSuzl=_r<4UjIj3f?O!Tmr~Cm-%ou*3~BP&FgAMR>OPo3h$w0#>z8Hv)=+c>2em0slMaDm5{wq!mUHXCCq8aigb&)JkP*nKekHW z#xxWrB(zKc3HJPR)gxguX7_!JV1J|{d#Zaj=9~(3}M6hN6v88dsNSI34$ne-M>F?Soo8(Ak70aaWgU*%rL7h;o2TFi3(a=tm z6`m$E**asbmRslY9;Ec?(waFCJAgRYjg>pPn3XVY7B!6=ihJlzp3U?ewD1s(Hx|IF znYB!er;_0~$`Q8@E;by;URxRJ&is5_(?yqaL*H}9i-P&T*ppeF9M3@&f{qM!qo!G zh~|MpQD2`Nn4%`z$DN1#JvvU!25O!rG)&n3ZeYEYQXz_@qGM2?YbG%(8%v+mh@rzT z$1{OrQm+q?Uk?mePgg~+o8GpC4lwY_b5S(=*2~BbalnaUJgxDSe!%&7Gqf9uJ`oZ+ z#dbb79gXAlf*qjqIJhTt;RCihMsBymmjRGgUcatg}@} zioWVreeHce5B7Scer*xW81Xy28CxPGxqw-oJ#P@}36EE9{7(kmK_f+7zRil;-1rm{ zEFiXvS+77lNSodZ`FkUkE8Ey`0DBkoNVh^(F6b+W>`Ds6SS-{Cp~lNtgL&xFK@uqd zPJ)VbrZ5#v<6E!8>M(;3u*oky)h=1nQbHOqa%QNgl11?A1G+H=&Cyj}*&cc9E8N9K zVUH%4E(8Ylu5lHB5L60Gy>~AWKzY%pje`?0tKk9YHXe(WT~-vOyYPfvv?MTsRo!-e z=D;Fz+`ROW0u@4!QMHZZ#U`jIubsykvYw|KBj+h;xFPT!^X1;eT)63s!X1w~~Qrnx>3 znlmFd`>qm(@hq25(pBp#!8#cj!AI~8;dlEfxs>PQjG@ul5onVS1jUDX(@r;sOWP;; zZSQ_pk8 z_sUx~r?U)ac25iEqjgRBwLyouXlTUatzw^8+zFCNj`8J^ONrWt2WAtKqpK1-i>&M=-_6_`gq@22!WAB>FY}O-`Yvz zxLIqIWlkcZ2AJ;%7+ZY8SnHh5cq*3WA<` z2az+%ic7Uf&DGF!dPC^*LtHLyNP9Y-`dSFE`)lF|JHw%2gcU*v3J7Dnqi7tw6=S zNeXfV!=q0WY%y~y?arw2(|Q>^v@}#52ZT3A1sP4XlHg71y3dU>9;7TrQ;MRpJ;+CE z&J)dbhwfogR<~nkWO-f(+-9qak>9ML}3UHIegruG7JF_XN`+8~(CZD;b!rrNi- zi2`RtaSdD?4VN;S>XgeJ4}+L{{GeL?=kcMlvE5YXA9G7_W41n`?Plr8;?b88f0k6C z3X>7}41X#r`)@ehjuv#0gOAnjcY6|$f6#Q+@5Q?2nuGS`bdz?r?y3xamM3-8sSnu} zu}`Li|5MLTK`LO!s!=Fi(v(rRj{wM3*k~@NSFTSh{jK%LjQ0dCq)ONGh#*zN+Hd!) zjF2Hobt%pQxBQ^bEB-#4`?-J(_Gi7Vuavs?)CUP_P7S$+pC4>`%R(zwz9)vcv{`6K z^iN*ZK{X!)nm@E?Y8afRhh4pW^SbnakvfjvawTtX;eB7i32&Qv+jl$OPTDWtusQa( zO8j*>cQ2NQ!cbzpb%!fTPVIw!N8#n=J4pT81gW#f^Ui^)^=G2>A~Hsh@Xv<_s+rE+ zIx6tHT@Vlyw}j@oLsWgg9Kal+5XHbzvTFLkV9of?npf6=YEdZm$!)K zA)hfqXP2nwlh*4n_;<{FrED9)6D2}|1K>F@rY z+RE8t`+(K-e6wtyH^*Bln&@pNheswiR|1(V$se}8!MRYg&3D_<9+dFT|0!!H6l5?p zKnecK?_@NOo6z&BqlAS=-y!?e7|^KL)kd+Y%avQ5Uhc#xvC``P>gi;We`OHb>uk&m z3Dp%aT-^Nl5)BtM^`$Qaoowqhf8KMC%B04(mygE0>CmzYAi|=b3dLk;sS8kon7b+b zYV9Ep1wPQYP2chz#wX)09T#r=8YAPM3gx>L6tFE)L8`M@M^XD{OQ*z zZ(|n}ko+{0d%HLwSh6(&2_qnpqtN5a_))$Qy)D zX-S^HRYT;GkERHCT6j8`JJQv0b?Jzx(uWZq#C$cII({cskfTQ0W3G!q^g-iVokF?p z6m_B>#+5&Fs|OS_y8pxag+D;ED~*d*?GFZPk3f&a(TKfK58D=%YG~{+HkIJbCM7SF zt}W$7EikdKvVyKecOI+Vf)p2fyL0Xb0a*A^h^sc`3mISblF&SN#=MO}tP z0QTa?h%U`YUy-EFt+x|9y{P66-~M2qa52c@`B12nv2JzX-e8HUFB|MI`o#9qw}kp33Ed)Kei8aL{_1R5-q8OqfW!R86%&*EE2o-!QI807v$P?S*47MR zXVJRmuY~77ZwWdd3R5xUIhJZ>$*fRj#jSicRq3PxwZWT<5jMHV|FDEcSjt$+0^+P# zwHcn`zv7lfV{Ntx8E7lE_*svNmdV7LpsD^wrhc4-SFS^DbLZds2L$WK!K~%Z2dO;P zq(RTd|K(8d;K6f}P5!f@k>%UGav54dUgF`E4j4f*m{qbBR%wtP#`Lxv30nH|gLmo~ zcVTL_oE?2mS!H+O`rrNo%KXJ(KW1MbTn2k{N$>|fH?ipT*iz`*mHEuLhL_?1)c>kf z@PT+LhO~!BIwNpRW7||uzv!lbY;2`G`d@xF-4h8(sW?{UM~7lpIl&87U7zM(%u4Ru zm`Gz15rv8V16>N7+@emIpknvmc!I!-Q!m?8{};kVhTlZdFMaB9oYHI#S?*D>vO8CI zy|%jyB-S;lIS8C-VAdI!d|qS){zaq|I6y@6vj0N1cWxBfhS+lg;!WbSTyJtUm&@Zu z26N39H>L>RI2%J;DLB7oW;070`refRx5}1?+oVY>{^rrJ9}Q=zXCZsqD6dG+ zFomudt^HP+F!x%a2Zy+Q84p#`5l%IgLFH|euQf(Pbl{kr3*FZLDBO&Mq`4Q1h4q4! zwuT`Qk3Fc$0yeCltuw764$8q&q?`>|o=O@j6C@IKVXUrExQZ2f`uQ)1``6y#zQceX zq8^H)q6Sm0q62vjY`Uj{sGhW1dg(o*A=OS6{nK|{8L zbEW@s%Nrl0KJB7dmg(j8YXbozOM|sC?u)A)pBsq$Y z3$k)D7^L?_L@$s^Jr4fYm*2m08N=+rDse(?4DuC7AZCJ0I(Di^&?;w9A8gRhX&ze{ zOxDIXEGX;@^R6oWKQ(X&WGoonlh&nC-xSH{?3+iMRldpQC!+s_S=eL!DNX^ukGDd& zW6wmoIt;h{|GNDI7G{MJgM@urljNO}w<~L`9$F%&FN2>YdrIh#;cPWvur_|!t$q~3 zDvUT;HpSh6WAyr8R@)cTXlx_bDWu}kYi6w!7pgBydboNgb^R4Iz9mrwrmO@mUm>SF z^+y-ftL>y^hQeXgHY zOPCgnKszkwnzo>BPcYf=Qp$r#(e)R)4lJBebHV*iy@H%@tO?+@Z zp4*l@oOeB7BBT*B0twX^eK-whkE5~9^^4noR{JkAU-TXxCmN~pEL5=nKEHCb?_$egs*CI!%@n`~YZV zikti#?G5+TmOPcsyGbjNf4u)(t}8t1dO^gML}@NX?gLC_pu+-zl0y`j@IQ{y^qj9Z zyhWr#L-(NGDy&DWrZUH)Etr3AIyxeGqQCW}3?#xL*n#M7wD|k?#%9YP!eL~pi3uK= zctr!Nqe5)(KMvScBH9%}CC@uqqPoN>g~Nsd4O7yZivPu;ob~Q8#l@=4N^=nV8(xOx zg;jZK?b_y}by+~5v2qm z5!WAeq!~HgB$hcyt@-D{>{s#1gM)e2wa|NSTP+40f(p^*saqS30m{|s&E;>Ak`GPG z-y^iE&1zNFuP;cxKHQz3t-tL0+Y0~}k3jgB1Nz~WQ99O1IIHJ>|I2eDSt%{Ia41Im zCsz?i`Ho>tnKLBR9(q$jh_tuz*QsktkThtS;&3ff^B2TZzxA7iS0>rPmf0wEqqVqE ztuM=5el`Pe0;0@D8Wq!$d9mX@lnrM#O?hjJ_`um^nk-zi4iC(I6U*KD>0!o-V}sAC zB*)9|jlkG~i}he|sL9SIl>axyVxqtK4bV&B3gi=aY8z%NHMk}K+-O2(JnZIMx$(M7 z<5cNJa4u^0-A)eA%dL$Vx(v<#4}0$!)>QMojamUknkc=92q-PmJ5fPU>Cys(A_z$D zy@@EjCwQoA@P0VwT>o#mc4qdTnLV@C zeXlih?_QC%Ws^R%3K5RJID9+I#v+^XQDR`#q>9fu_h+VjAGapf`YR5;>Xe^zeg=u9 zP)Jz~6)VC@S`S6u1nayacm>~9N)vw#2?ZF!+GT+Tz&et>LO$-$GYJ-tBW&jMm)2X0 z80H$!y_(n6T~i;JmwgoAtS_`jZ`;N2?&6#uqnyfy!@kN`{u5hY%d0au6RzXyufWK@ z+Sjj%f)8#~N%3?fj5wIJ1A#3F_LSlq*B(qeH7`LH&}@DwB`u{oFPzTp7jMTu7kk!m z&rQC6SU&OW=7dM)Y04Y`eYRnL)uQkk!@^QL)Jc=o|1Ri3JGAt5*r0B*kWm2ZP;ksk z{>zYsX^4e5OO2(k;-0X`Mu?hMv;86qc?TC~hKRSjozmqWPTqWsJYoUQx5RJMpLs_z z42}ppJ~Q+>r#Zu6ZjZVDkeFYcI(qUt5>D6K;>XXuNP=o6IEptQPyht zmYQ2iRlt!zEn*EE%Lr+%k+K`z2r}2`vD>{74Xqq31DM;7hcion1W_p*GMp-Ul%?9; zN0!=P8yB-JglaoBtMKJPnKK^vH}-gK_{Q@mQP}G*XZrZ-%VxnuyK1;=l*9PZy)H?O zg_jdocrpV>mHmap?!#NGma*AM%Mj5aNX~~HkZ6}5^Y??LVZDy*rn(B>$*HD{GfZda z%B1jQK#I*C$DVOol~7m-@)HgzE6E;df?kNMPHtK1sNE{21@f4%YRbpm)je$ z$msE;^u{~JW>u@*4Ds?lNS{NYRfQj%SS;~*lr6CwS$gBe9C&?_jl(>tDD$=MdUhZE zR55I2Vb=K`kBi^F;CgJ6sR#lc3h>eQP6f3@VyzJE62yM-Pe8->C(2qH?aS5l>%!jD zoSP5T-)IHx@=a%0NUcIs`&^zK^%^`JS_S&Z@evX0p7;%B7dfV?x9&aI-vU)NDROE5 zke-qS4?wj1`Zf<1kPFqq)kP_n^M+_iXkTd6d-++4Nx zrr{)cbH6gh!lH6JkAbqQW(*?yi`{sz znWk9C)^9(0>*5^U&>d6i87kHRm)T(y4ue>9iq9ZHiTp58C)S@Gef z#-4ygeW%!5VzhogB^WZF0i$SsS*g2duZpbU5&a$cU_E^2QNHt$3Y5R*VCco#2Y7~%#t;$aKU(qT7 z2g}^K1T02qc>O)eIQrY7TF-=C&U2-11o5?eo9x!8KVjx&vze%5%~a#F6**UV7qhn^ zx>g&2Cb3MBHDh@?ldTP^bA3=gYS%hHi5-sI8NYvaK~0>MT&v`HZ6Ux|BA-vd$?dd? zX0n{%U=c(lxnP$UCyItPX4iB$^OuJw*IR}~Po3(EhZZ)*OIjvLJEa6FD*SW7ckLoJ zEJvo>kEu(V&l-KT`UO_Y%fqxsc)K~icQ0fkf83tvlelW>p-b&3#V7&eDYI*gAue}i z{TbHi|MPxUCN#TBS%cymT^vMgklNqKp(!4yZn*oe=UOI#DGuuf}<-VwSX zR7F9O;@ch`?~hkNQj4>TQNzl z-lqZLt;KR*=Af-#mNADuQ2mU}oqBZjj02%keFgB&Zqwvu3I7v2gV;xAGk-heVRX!(>Czp0t zZLpcIwMDkTD#BRRK%&E}Oe*A#xdv}9@V7Ql>)tbRpPpTF@G^5U>HHK$`ts(-kDuS) z%X#^2A*+M<)mymdcB-!#B3r$q4IMY@rnI5c+dA_!?^9S%vFPS4+PM;kqRw#0v7{@F zf?@8|ly=a@h-ycua%(h@i*elMz6MqroQG=+@GC*|9bXI%QGp(q9lUgpTh~gy$tvQY zS!tF$uGFX!nA!V^>i9b4!@ZDv7n^;p>Ei+;!v|q4!c0+A)<5h{%$4C&K zpWkIrI3=|SIG0Xycf7p+p;_^)VwJSCrG&rCJc}^8J3slQ6|3&V`SP3}amwt5+g!>W zy#7<}VrcRQda=<40dY)sMzj)kvxoCEo^V9MNG>LTrYr8#7&F zwtMwp-AOPT3+sJCTj|TJzMi1nh8C;!2(&d&GyE+fseliS^2?fj9cO+0-O}@}C$CMh z^4j0*hfY)ET<>y}0rT0&)aIgKlAkf7Z_EKT-umak!AJxp*<_|7^W!b zh`gO0lKW;bX4#tEeD1}(; z-ni0cHrCq=%2Hw6!NPh^JsR9%#UUZ53<+#TD$z0b>@Ow#D0g#A*xdTA=@1pCcq0He z_1k*7vJ;w`-`fV1)*Vq?wjU&{t@j)oa;4diTN&>C{Mvzht!?P%eDy?U%P0(*nyMpN z3?HuimQVJa*`0klPxv#O;%DzeJcN0uL!tlvPV+z>`|hgn7L@_+aZ>rhSQf{3eMxee zjnYzad9%bhp9VcmMUmrgC9!&tJld#hSpT*6jDW#u>(f9(hoVcn2x(EBD-6FLFEsx9fEV**Z9Rx#VZ_cc*fDBvK`|)f7;&;radgv6j zx-R4%RGTk*#Q@gzUjKrc=w~&V4xWzK)5|gS*-?-1>S8Y!PUmm7QkSPIrVIL?TK~YC zGaL{ppDtK*$4_1vI{Yy6^)~GdJmyQCe3gZ?k?wg&atj`jiL3DzT7x8FYfUN78fIKy z8?9~cB;WtcCQ!6pVv^O{HgdT>l!pb#B&FH4?&k$!VvJIAReq>3xHrY3&RK*h0Y0 z;$$Qw0{Ol}ZD*Q|+PgoM>%p11XqIqbI6*vN@54jHSPp(HKxr^bT?984pgXvqfVR!| z%M)FT!O?DkR-j3GM@NA|mi&iBpsK!_h0wESC3s5!Ps}0~Rir;>pwO5K-RANnlF7|h zGf*xwN7SciKZdshT_!HgX4kVXR=o37Zlu0pjM~gXFh8wwxj*^I&||973PA=}^d{C9zK^f(UI5okfq@>MG~txo!awG zVsA&ey$9XXKcQk>zBfK5EE%LU^g#-ki0Q>VkUZI>t!Cc+tC8CBs`}KOXd8D&1I)VdgbXW5gvt94r8drtY%+!ERFZ#=H z+N?>k;x54~S^UTmU+fT9_4r>ua_LTl_ds-!T{B2PJ`Uz@Q9^n$OXInME=N&X)HxV# z?Hu{Y@8VxCc0R>52gJP9*Sx&zblQJyc;U7y(<&Z$&CS=%bgI%edkCBe{mLkc zPlkO+%x>9sc0LQ~%cFB8;75UBq1`Vq81?wA#5-=64~)(JF}6=iS6)KJyz0lzRM!cF zA{_pLB!RWggx^ecA&T9&*ydp~+Flz_NZnBB*7ePRU%6?@LQ1LlOPuJWCvA^+nG7n^U7|4=LKa^>YoE~-M9Ocui=%ycyG z>w7S}CEhJIXO^64$tr1KxAsL6(MS+qmbyH@&1>@P6jMj09!S{rj~GFL}) zsnf{yfg~zo>va=^Ll*^|ILpvG7)+oTEL^$k*RVyGbars}b+@O>Fd2?4}z?W@+Ag zv7-3*vHhRWm-nx6uc&wnEx$8VR-##7FLoO3rz5)l(F3`nh|`U&;8G zN@&(1UPE>LN7Q;<6skP<@mIYKbx&Jw(vv#xuGd}!!FM%?L#~bPYr7l#4#PIlhdlB- zZf)a=iaNTI;t^xiZsRDUnp$7UVMI7>E(6&by`%o2A2^{t6sI77!I!q0*ynuA|( z$B{1OOqw^7MUC$J6`bNC@uS?hLi`|8q}8CYJyxgn4Xq_10Fna#g+?cibIPPZNx7r1 zG-l--+oKb|U7lx7?vxF?8o3L^d!H43_u6?!Vk-Q&P~Q z`{$J3!faeqk45RdjjkYrKFFop$9?L`ml&>YOLDc1^W2llG%9Ap^pkQtp6QQ z|3T@vK;fkM7j>z$00~ZaqZUkN>F%#8z&jU0V zVbb~{UEE9MnPKR9PIbJNNU!=KgW}|Wq(0ug-BlDnV@JI3T!5-PE?r+VSEwVrtACNl ze}dGMSL(%|*pe-=(q}f8%UG2|S!ng6l<1mx-kzYIwk`~e8vQrso~Dh|v+q)d1K(R-VO@*3aU0-N_u13XIEOXdgi*5g#+Obd<9nxGDq5Jh z6diqnN%&pqEmbZ>K;rS&vb{7#wp-2KWW`)MTmZYU(bGDke)H3RM}mZwutMS*$KHFi zDIQFTRk;N;0KCBF_usjZ3DXTXkBEL-_tT3k9v%9%MyehDCU`7aE^!tU^d}y-w*m8G zCK3{fx;kzaDpw+cke3)3jA!=>8~^Fvzw@5JIx&*AzbIIzV}0>{x4GTFji9{pWG`e) z1)RALIexLNp!8oD&7#V0Mx*zS-UQ9X+ZV;oDOpE~tJ{lTjLh6STOhnall6O36WI4t z^V-0wqLn32z_)FO|MZdait<^;`qTu|PKR-<6z|&8(pMbnDfOIR`QwCiyA$-xOIX3CSfRCbsHe zbWG%ci5}~__^$P!yf|9ubFRJoC^u{47_srT&VLeW;uTc8^CTk{^Q;Q07^-_Jl3}#4 z(${_@DB(AtperfEo=JuI1#0{oHh%}1CQzLuu~xw~!&KYnH*PB+r^#owz9>DeE}PL& z>i)05jwjolwp?_t)dDR0E}^mYAiY~PZj2iQfd}aHjye9{Infk(VLya6Bvj>Cp`4Il zpn`HY*yh8^rnRnYi0AA#0 z+o^7JucK zZQQ+_kiwSa&Q9!>l7-pQ_Ro!?fd@q&s27C^0ph5h~aD-ABjn|EyWk8&6A(f;PfNx|9s&F^J+(7m-5a zT~0w7m6rV56%S;IDWq%qWcr)=K-=KtC1g!&neR8}=Ws{T<(N#RuFD~2cMNBkWpH|% zu02a2SwG?67{t|@NR}u)WR29xXg@4+ATwkkLC@jpPI4DJXLM-%3vlF%^+^G*15$B^ z;r3kvW=Io^+g4Auliugp5z!`1+3&AKSsHJKY3ZVS=EB%p9T->kG)b}^l|}wnkm%=R z&|AkytEc}@!fr;L0SoVlKZ@>H(#hVXj{6=fWF{+`zev4n@x(xA!9rT&Qy|#}%@D_e zNUePf>8{HvO{CKER(a(|1Ir_x4wHhL-k!YP94Z}$XwS$KjPN~kKy&P7bFH5;E7wAM z4LHbfyvZ<5;fK+1Lw(Ks?Rw0FjptU?QX=rgyN6HkRA;F%sB$CQbTK~&)Q}7ixNJrY zcq9wg-8z@zX9m(%po%*R3XEHYH(LfCG4#77g_P_6nS^$a{shxDg#V9%X~Z$Qh^rL+ zZ=#->5r5>n9hV@`Zn3b}XnzfDepdwwCof>73u2?SG<`zz0JYgcRrjSH7cT*NFI?#T zCbFVJc~gj}L>#_6?*SN$7V#pn=s~8xr3aOl{pf1&8M(QrT*oT+uI5X$;rZg$8Jz@F z%xr(z?%2d7IydOGtFp_AtP{St>vh2sbFfFO%`S&Zec@{90$$-z@ipN%1OQ8)OkDil zr-ZApHKdsJ8kU;A?m^e#TE?yJY}FWby0VKgf77=x?uSrU@ZvDrOt9TM=v zx}Kef!BxXK%wWxZKMl%OXNtW{Lf@pgyyC19GYQbU?F_V---y=f)~PhZ?=#QNe_4oT zaz)CJUsUxIhdwL5?$E3M-V3;YDo)NHP~2!klr6n`fh8S#28Dj~Kc;)i_+m1mY(h6! z7AE~}j!K}`mD?_6V8H7FciH4obA_R>`$N;a_RaYgv}Tn1j_HY`96G;e;B4qn^A2}#mPK*&*)70wQL$PG2(edg@F62`i(5?!l|x_s1Vii{F=w{ zy!G@;t&9U6&mD;T6`c<^S8N2I-q%h9ASN$_3|kkka#FN^wz8^x?0*YT;!h7)Qf^JW z$W~n<{N$?4W3c8tG23Z_Pr9R7)U_ok0Y8&VL_qaGE*`(J>O|TWm#?wn7y4PdGO7lS zLGLyOUp}<-+rsv2IN#l3{X3cDnxcTOQ}Ye&A`Ij68(7XVvvVg82?o5hPGdPumaWEf zM9pqPPhtjXv|xNT;9O@SMb?P0yEPra97qKYiD~NR0yz4zq}9DgFdNj|%TuEwW_j{u z#n-9t5ofW8Cte7@WE`C@SsIZWdB2vElL0b)e!=c=iUnWo(oTU@U@KDKSl%G3-uq-R;=+nS31|;chufu z>Qb-Zx1RhX+0w`R*TF`{G6sy0J_fui()~jDD^8{gwu5$%n&%`w;boEf#A44tE$>W- z({(6Yq^IP8>IEK^4V-g%RRFQVj$YN*VTk^N=O2_WD@0@BbWjT)9ZqadR|EyK%_dZQ z14fO%_!wQZG{^ddWk^@0DjrQRyY2&;6 zEyO&tEtZw-X`Z8|`XWDEj<*t}=S&1zE9M*Kx@S(rt&eLZh4p1geq6tk zKbz@&aU>#-Jd?nqb+WccT)^W{%&LVnK-ev(#S z0TVJ$t`BNBf&Cn27S%!ucIASz`Qu3&M*K^n&v&27Bs#qtED+1>LhSPWVSdkp9=<=xHC*0dMmM-E zWjM;(GQUULQp0@Plzc(TqsNLbPq0&9RA5EmNPt+7R**|jOi=N7|WwK5`egs)W>fE9|l0Xeg|KNmItXFkYwS0ynCiGv2?u(CfAO}9B91aKL++kva38OH|sV>Z>QDH6&fZ8zrZCd(s;G`F$lP z6K7G1lfx$Syi?~iMl+6=aFUDj-SDKb!%h=ew%!xzPd0X(@nAhhmBuUaU}Q~IG{K7e zF_!P%hS|t-$<94n&iC#d2{CC+dVl;r`B@MrPgmm$xW^JxB&@b0sipEZeAOkN@{F6- zs3$6>^As7kz%=t%#VJ@-3Iav>y&D{BHin|syiou=OW zbGwE8l)d1${=)#+Ik8UL^j^pnnkdjK%G7p(bv)E8jcJ zJg>7rbn;Jg2{!dn?Z12^RmqG*6nu9W_Z1*m7*fL#P68HH*4B(#yv{5YGuB2>jJwv>-odI=Vl8sF;DKl!!Ur=3RR6J zZk8OmHKZZ`!#pYZFSQ7829#af35{8WJ6QkH7DLx|t6$k*c=IqfsKCyBP0Pi;dy{CB5+@hFldEMj#gS0)CGvA!_JCr4C=N z+*A9N6Iyp08~%`I7d_``xDtf^peb1OZ)6oiz6 z?^B>wyGcFFR<>Jyu*cKhZA=%hgkk=efgA!QJ7be(ulR`guD*9OM=5L+^<+XubUcukF3)6t)R8-ei#n%D5Un=rjQb7cIBY(q~ZV}SBcQrCPw7FC& zAZ$Z3mXcb4yubrn5$_N{#VlnMOrbiQFGMi#QTHS!ck7cLJ;({!yN6Abf}&tt=48|_(l3BN7ziZvHyoQSWIP#0u8ejYqhe%5324+m$9J)hq zP0Yxhr_4{#Acb5N6E^D7{lJG@F)@+95=_DR45CFmKOS@ayU~Yf1C-g^fx$!jIRnoq zKt^nT18IU7NNl5M`q3Os533^NIWf6M2NQuoiR*$-&}+2=bh3_jI2%h-JNi4B>QcI0 z7-YR94Br;D^kGU~^(_w2iF?x|0Hvmr)a2V7>vumn&sKPnT0})VqA7Batz!EfpXqxm z9vTusR^6u^FXpWnjgq?P?8rACE`3j_B}$4L`qZ;6ARl|V?}-gIQM=ZM)X3)*d9dQt z&02YI^av@J*_|R6;RQ)Y!RlN{G==@1aRR5Z7a6jqpyMYT6!rUk9A77xw(M&sIp;j{ z%}Jhc1&t*`eg|FCybg^eoT^ZMZyvh6EkVmbLnUJ(*xD!GQe&w>mp0!p@9?X)=!mS= zsV4KYcrm5f%x)$}0Yc&ckm-GukVa6{NL?q@;NI7H=!mc?AeT(ru*lX3$OiO#{HU?D z2sShG@wP|+L@oYcHJ&grT>fHfOk@1wyZ$oImx1tVJ{RXpalVt?pt8QMQ~?J~6BMzZ zYuISW!BqtN*YRpoqb8UO?_rTa)flAjZb zV^M>N+#Up$;#}(1uTv?bIo07aSmHU;A~!jSx*?=O1LrotWYD#&0?Y@p*-m@cvQWbEWuGiX}`Ohchl*kk+J;ZCQGl0Xk&gdGobA&E#xH>Inp<&Y>UrjsIDrLU9-^K3gB}4>K?d9}STv@m( z)h2PJlT|XeO-hWBF*ibCyHn!xS1J9IA@K)Nhbt7%4sO^FIN>cBtZ5|}_F-SQ{bi{6 z11>P^UN%(^LFSiw^@0a63drP1XneG8EB=NQp%RPYIGf~`@r~COMlbAJOuZjoluazh z-eK}Y)rW6=VC#6s^mZj?(Zrq80S@_DS9z;P4`xiQrL~8jlxS4<>EA5gX}+b&MhmCA z^fd7W)ook(#|_aCzFd#+o)7eF-|vXt*IH?T&K$2ip}!rbd$nlhb%)iXXZJrpE2DWp z?#eBcr2OunWnS5eILcK}bpp0yxw9`p_`s4^nn&a5W}I>CtR6+ZWf8`3wkmA}m*%k( z@8|EcJTbj>802*VyKr^bp6AE)%1f+em-=G(_7%4qSWaY#Y*}VnwRtIwT|}}&5>ben z&2RWQ*yi~hx*1(Hif`WTJO-`ilxBLI?f?Ro3pc=FA2UR8SYa3%mZ`YqsmHo*002!A;0zH>IugJkW11W@AP z_;Nh#NWHYtmp&W3t(;kWL4(B_$UiiMT82n;H z$m9LN#wlFK$osPnKx5s3|Er@9jfV^0rYZ0lII;Sfv%G+6pM@e12tG3uHrDXn$^W$A z8{QT3Y422b=qTif%RAs`1Ao%H6d<~f+Llr}v&5r~$1?%4_>-)2F^B{1yk&b<9bW~T z#hr>F>d^2rYf1p#=mqPjP{3mU4&D!Ij5`AuQyduk>|;1^gWGBWoacRZfeG%w1KhM4 zt_U{02fGP8q&@H0-d7$2o=t79<&gU{9F8~`Va`mBdX49R0ts$zk#awP27x;1w~v3^ z20^Kk7<*aCAxsofErk#lOC=W~z^I>Z#IYho);}WA!Eukzbfy^JPeix-rM)qhjgGq>0l0zL^DrLYu7=$~o|yBMqZzM*n3L+Q+j!nh;*gydT;HgX zjBkYp;Sv(C7qvvo(r_4s^LTso-l2Z|9GM2d_nb4<^amU+DVEe97Q`6EIEXYJw2!hh zoOdI|EKgc&Vvg^kN59Otyu`Qb23X-$7O_#%2&*%+vG-hE|30ApU=`!B>I#}Yalmh{ z*Y^Xx=e)LY6^bh|rHG?d+Q#ESx0vaDfDAUq;VeR~{=ny?0_i7IMwhk1pbENp*24_kul$0SUJVa~T3mP+#brCpA- z4x3MRcGBvwPlm!;q_SscaVQ6WpQCTU9a%ej>(l1#h8=&86QH%on+q3Mnty#GV1Ik)o8=lj zZ~4*0EYg`~-Mi`%p+CD$FHKJHqQ|F5FGLx*u?Bo*=w#7_JJ&WMFB|*5()zyk977z* z0Xe^E)OmdB*5^|s!j4pc)0Gw{Q$vK1$Vn5JQSb)YRx5My=KjI<5D=XNC5t#39_rsr zYe){Wq!{%(g<_E*JY+EaU`);tehB6~9QSGl`P`XW|JQW$tiS#ch_VQkSc%F(f}-J- zHt$=23C*&2L?eubb>8 zctf7CgovO}K}N1}ReGJj-&Ex#3Z1|dp(BZa@*S-yC8AB#V~|(JgbH!$%ww+K4}Wu& zCI3>+a)^t9Uu0FzdQkxi8_wFEL$qlq<`JjwQ23$7{m0^Xh25WrKjL|NX{9G-KpI}@ zkEwCAZYs7jVqBTS6qPu&+muBAUOm6eOwo05GX-ClK4yveHugrejV&xsM#LoNLy)|E zn(Q!xkI?NBJGCXA#sxz6f0Yl(X?mj*YNc&191t6a5b-F7kb@|^x0h#{3^)3xOHTgj zjBa3xNQ~V%Skgphb)tmNf!EJEw`5Ger0@5e{AIGa9j*NnS$mTu8y6=|O}~kn0H%Ae z^@?G+n zBHZxzqjQ_y_}>%V&x7>>d>qPcF1u?6-6>+e?{Lp+O|ih{Nb5X#O!c1;qP#8+tnOij zYxu)`STBoG?1XVAgp|M;hUAoOX8(Qu+v|nBBrYDQz-=c>pT4)4Qnw1}dN`GZ-*S^+ zF(xx5<)4xi27y)dpWp*$kKv2CizF3BeVl~y%yxBFaXc?kK4WctYa>&Chf(L}=S!J- z?C$xZ`yR>KD>ufbHa`cf6Nzs==*yFloc*Uag%5t3^my%$E%*D8jY&(3ZK%wRGSMmZ z@dlV+`v19=N#TWkGrQnKmzgmRGl}KF;-y%zgB_cuVrF-~&J4l-o|FGQC;$I6C)E-z z?I2FKQHYBs~2f1Bw^mvNbZ-FTYKd&d<5~O^$w;5#5GYmhHCo z;+BhyHbr8MBl?Zq0oEcubgobaLrh*sk(gbI^eGg{j`Jtls`+C^q`_BbyjD`DA&7&l z=?DNqoSJRrSs(qeTiQpD^D%(UAeXV3P4?k1*I3<2rN;OaV;O?w2Gb3e>SfwDp@B!% z1WWnLUzl89bo3*LW}~+ih2(EQ_umNHta1<=NAHtHYA;-_L+=m2HGD9gBTe$yz8H3L z&_M51K?s*xx<&od47Tg_gyZR>Ffe^_P(ToK?{xv&RA7fb{C)!BMCyST>@q4yOG+|P z><%sYxxV!J5a_S#p)PDPaA>GHMME0v@fD@5RcDNw3Gh!Tk^w#1K4tki=VXV&L@cgR zD;aZN9ijp_MT3RrlN=`h*qz-Aw@hmNmw1fF{kC0TlLFvIOp${j#bm*xht4qkRZ0L~ ze1UUMN!uXA<&)v*6(}^TKbSrT7DB~J_R87D3p?BlZJ2cUI|T1;(~;>m{uDkI8_SpP zqg_#9&aL9Kj2Cg<6pLt&*_?RW+Z!ET{BEk0h3Ey!at8n9ExT-aPFp(SA1_Ls-89;G zjxqQ(1}D#}r<_ySzPg|&b;-+k`~lqVcPihvR+;RX9Jmvc-JtWf4!gLb>DSO7*>jO7v+qbarUE3 zXtq(_CPub9wjI4|eOlZ$f<@S?Wn-hsm z?l~h|D%|M29$CroYGGZiZyzaBHaIU-2|hNNcT1;Vp&ahihbGL>FYJ4J7@H;#_Glkl z@eBB_Tf(~=qUpg9ez9J$LT^V2eFqc>skhrOV{|5|jJv|C3JBpAUEr8nk%oGi9#W3B zSekFYo$63Xq4yZB*l;+|72A3S4Ides_=F-n-vE!TJECWhVZ2>XR+VXK_g?0`_x3W=yuE2L)I zAln*io`L|c_xAH%8iz;wn)hZa0zR5;UMPqN>ZBAw{;GUgTUM7;IWqz>U%is z`9vk!x{%=cU2xY$B|_C28Yt8JqB*~lXYt}&4nkFf2o-WC!o9?$b)B1jhIP#~BwaNE zIEgP-hDBJ}P?(`nR=xB)*N_yuU{tuCuGgZ?HjSmQiJfovjJ3OVdg=LHaYu3yGhSG_ z%)K(1vbMu~S==#znpS`*0dd-TnSFVl6%jUm85!#nN)FuTP3o_>msWLb7?5eUPLOGo z9?eq%0z0oCY^=oLv2z})JD&O*sHd>- z(y38VmQJH{WEsDJsqbvX+FUGPbj(+OeH87p%>grU5yDOVkc%B!v2gZ$=c8^j=NQOd z>m^|7O<7?fE1lp&*E%{&oxpl{_mBzORYeO2S9O4wM#a5^8jp07ptDP4&b#_}#;5r- zxHBf3IPb)~WRA2mf831!j(kCqKf!sV z9U+kL)Ltjfb7?E$8v?Vjl4A_ibs)mX%s=wz)zs|h8~}|08QkaeWZio{Bo6Z z334nfr;^{R4oCPuZ!{dp0Lo+&2x}SH?*eQ#qBWAJVIb;bB=O>VFyB^7g`Q|{IHcxk ztX}O(H`iEcA*OBh)@T*<2(|b3?qgi6uIbQ2j$1`lBy3=VF4-^?E$KI+D6XQ?K2?E% zz!e$bo30jbs(plPcyn*o?a`M|FP~S;$zf~zX%5VEN}~G(da?8_tCf=c9w@UthSo7z@E-(%k~^8M#aRZzt<%KI4k|ctGsYtN z_+Mr#ZijyDl>4H~h%}*ur3&`w+|dojP(-%&FD_W7 z)K}oDE+RP}xQmMA^fLLgd}L{J7J|^--ODj4w5e|NT4&A$ome%mJy=d7b&Z-EjjvCh zyy=0fL*xkC;`D*W6*+0~8VDFHrhmV6=iP@Q#Lm%j=-x?byI@Bj|1tU?YVG1OK}Q(A z_?owvqsFUS3!g;cw~hVI}fs-$_P~}2?zXO(R^*{x$B~*1Vr7JeN8Tb=C#A7 zxv7CpKGi2RzVt7;mQvh!V)&J&XcineY_;59u+k4cT*tyMs|@IHjF6ZZ*uVarN5}^Rc+Ov$CRa(RFRKzl=rUr01&x z%c;iNidSV?JDmYwIS_>HA#yDBP8veh@4c)uKJ1K6sCADL>NYepATGRWyBd@RSuw=Y@=MSST|iaixcxIRGO&)OtmTUo*&L*j|r)n*@qsa8d+~#@wc}5^3oe zS(tSjJ=&iw!H@GS)3G+_w3QOZ#sqxg9|K4@Y5TbtFXyJC>9nv3r9&u5o8jWzk#Eg| zu%+6C9lIONlph{*7JpER{s2%!NSvT6)1vAzrl68VD6yG_kM%hImP5i0cCvp!xqxa0 zTjh^&lk!%z00Dt9_>;#j5PyANfah`jY(&%D~q@2*BGRG!N6{)LV(D7kaU%d?BWOYT(sae;zMM`8IVoGFV#wMi- zJj%M!m_2fTl<>q8EVB%|&95h}oIG9u6O2mz4ziU2DWqBefQgsrEA_lMzbJ8Tm3r2L zu8-PXoI>vinvA#g`hIVpY6|*VrNIC0t%P{h;VxtQ{ICRXUM5R4ZZm!_7QyoK*s(tJ zA1Ecwa@Et}(6sXtMTgHf3A2X@@UsJM2oprYrq?EwwL>5KtqF|P--|mOew)P>@+!(# z?0sfQRfnW~%Ejlr#H8Ekjt3b|54`bGZb|CmpOXqHXW^R zUf`5PrxJtEnPZa9J#V|aq$T(Td{CYlOiqHR&hlKhEP%V68rhi5{Iw7jrk7JUTBAWz za`)R}B^g2&;ol89FPIM?Un$wD`PRZqs!WsAVEQ^}@@IJ4KR~&b;;QfL=tLZ-5L-VQ zMEL4S5Y_C-V_6q^17LTY89b+Yo5NBKvDk^ah6mWEQiyyVE2vR`Ir`hTxa;ko=AzM*K z9TSVuXWc@mXzQB=(q?wfivCOVHi{x)+G%B?a6;57vMMvVjk_NNo%&=<2%2fAn8sTc zs%(ERkv7}P|Xt|Hbq2Y^+wF? zXNC%h#KYP;jM}ds)fg$AJ8twRrV|V6T=>&n6j5|wP%FA8Heecp7L$aK-84e zvPNn|ot)>7ZEQNAb5)~}Vy)lKL4irCLT9z!=RVumh0zxyy`I@aH)>R+7J3~wDurhL zZ+|kRRiR6H`F56g%{EtGq`6#5la)EB@PD6_Lj;Q-y9i(CcOm9wTv10qGVL&kz6{Ce zdc;Jg>+KA;WKP)@X{jhaWb4y5bJ>=PXy#GezArW({m^F^9Hr<3lInKD?Nvw(=3>5S zq{{+C>~UBO(3{iuonVMVq?^F5bJtMp&}g`ZO@o9j`9KmNKufJJ{+z*LJ7HSVWc)ty z!2N7^%0Tn_@)T1m6+faX;@eSJ`{E~;^yGcs4n1PnQ!>)xbC&yw4Lz@@m5fzQ*F4VC zA6duxZwz76AIVa$9qsRR1;`%zE$`d{(mEjspEZPd`K=4VWHIyxngRLLQ19X{_e-8iDc_!vj@sS7IaFSPIaf)WT9p>+ zaZ&f!{$e=_E0`c_`~$1RNpT>7#I z6lF+M7&RVwc`@57U89e&H)DPGYsuw-dI@i@W~rq(O9@aE#p)g-L}Ev=uZPoNx!=0k zQY6d+_t)JT;FB!EYW!WUzRXDimiQ<3vr58!)wooRRVO~Fkwt*9Z3g^DpOs4LzL(|8 zq+6aziUP|>c6Yyx(f*{edC3Ktpf%e!-CYrH7G)OV|^=0RJEM*{roZex;EcrM)Tsxcw5D94 ziHM@FO)3MEo;tijd~cl*W@ZWQ*V!jYx~#|I&c{U&Ez?fO_3)(>!)zw8%(p;33Ml6y z{5*P6u%aj zw5ixGYufYk5xY`y@)I~Ml3^}PyAP2&99R}mba}R}?iZu7+$dNQEindCoMINZhI|D* zIi1Z5jf;GDlMNzz^*X z5NM`OI(fdHbgM)o+`2c6F{j&oD`Gk*vd@XnyJ1Qjix=YIHlZwt5`q$eC7tj{fXdCGQ>XI`C zBy_yHWUo-Q9&N+x@V})nViuL?kNE;gJZn2B;i z6ko`!3pY=ZwG%>(qI{#`AQEkNboU)Cc;Kl1+ML~DpxSA^<;Ey>X`@V*HD})6Ul!3% z(FoN<&KfTjw;MQjJ%{bTPVw*cI_#!`O?;F6QBfBTqvrBhUWvL#T;JhrS$pzNdWSXY zQbmUL9)r(BTg~w>hl7&zgp%#Z%#vE$xTA1=wF$fGp-N-;<38;F*qMjbSF1iw^nJI} z?yp{CY<*|d_IQowW!ZnvhrXF{|Cj9R!n`j5Umn}JhyUZ0cst4X(xz9YtG2HzdYfM- zGQT->{>t|YW(k?8q}jSv9L;)DCc55mkKnep+q zjXeIjhnGHx`ImG57<5?mT=0WC2TS?tkH{v@sc4&jYHsOv`C}VyUMlV_zJ4dt{N5Aj z?3Di!rgGW7>{70ct;*k*#7d{Bcbf}r`?6o(PcGft|0hQk-+ilG;6T~mKR>c8?tgm? z98K6)cTP4h`p5U9mj6%2Y3URRcoAe1v-#DQM=%!!=5ew{FrKF+OHh{=u0x|&J;q{7_0 z4<{R(nb%T1<8Pf5c+C2NqV|W4QZx8}ieLPG4>l18on0hA9dKWJ;6MAG7kfj)yiRg6 O0D-5gpUXO@geCyLT4MJA diff --git a/vendor/rails/railties/guides/images/i18n/demo_translated_en.png b/vendor/rails/railties/guides/images/i18n/demo_translated_en.png index 7ea0c437a565f779adab08d522a53fb7530dfc8e..ecdd878d38def102e2a4c2f724d66fb47280acb1 100644 GIT binary patch literal 12057 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 literal 34506 zcmb@tWl)?!)3A#KcXvo|5TDRs?Lw8yQ-__p6Q;Ro}Q{}BGgo5aWE+{5fBh?@5)xyb-H(d<2t>A>s`b(Uz6VF-%E(b_Mkpe)r+0N<-x4 zSRt&sqbZp2Vi+NWpUX5^^VB{skLsy^9uS49bszX{Kz*yBxln^eVD98Y)`jcDLk>La z#1FZLc~ga%XbT|_K?8koMGfu1djKOW0F65kwbWaQw>Qpm}N;;-er|K8lyhsQKIPOSKv?zMvoP0a=W@xkz)!% zAUWdim_rH$jMbD7W~3nY7vr;Rq1R~%iQYVvG@Q~P^eGgg(1-q<9;U5jbVq5F8S=JJ z+hWDf@R?QaH0~8MIi;h&ckEI+jG4S(E?I&D)|N|7dQ~F2P>A7gWjQrLRH&||aiyhz zQND^ofNI+(6=p?T4!UCQvhUTJ1%3GnYJP#%+E76?;AIJ_(MNeZY1uCVnl;~YZA&eQ zZ48`v&4(B-0DS$_F3@*5gOMipe49c`Djm`q1{6&hHo9-11 zd$a4o$#>tNjskQ1T72Kux$IPIuAKP%-a>!fZKXD+#yN^lcoVLj!!&QwcirxBr|Oo3 zHux^{Z!8VHu0OQU?isajcmXYIMx1bO3*c}xOwWtx`oJ?zMRRcpKd|=4%+CdG#6X}Qa_Y>bsN%l3{6 z4&L=CyYM?JJh5!sL)sI$$6$T@F#n;Z!nR!IqC3u5xc1=pYaV5c42O_|akl8Xwy!3# zu9RH?{!SEgi*jxTPWq(2y^bmFjZW0+pXKspn^h_y9T8z#7nf^KW@@JP;MPzYEH9BG z+5>`%^uy%C>_ehcyPXmV6DEIgz#&A59Yc;O8=LW0+?#ZbPeI9_??WR)uR6MR){iy==LaqaDB-Q16Wd}PoWyGw+<;1$gVM)YE&8Zwo=1!*-+}-k}qWT_9?fVM_ zt2K5*c4Q~~7x^d47ZIOI`7`)@HsWk2zO&g>fu`;*fvX3yV{rnj>Z%mEb)WqL3lgiM z9#BpP*T!!TV1&|A`s&HFl=(XZB%UtZbt=$f}bjFGj%-q1K-Nsy8{MO zB_Q=j0I#|jg_z}o{O#=M0okm~$E^lZH$u0$haVw!#s=Nj0cVe-b*ybLE4CN#3bcq0 zXY2rkTBD{lyNJ5D9`M1z$8k89ARPItGv=p$d~Q*8;sx}u-s~+7bPQ=k!mvt7iae`B z&VunHio;ckZHhY{>@?b911qF4L@nF}t|i=Ns#7w-pF-Jfw{4bQXZy?JQ9DFiWcZsT z7DQ%*Uoc+#I3lB??jxHbQ!i9f(G%TMW|kNlQ)MWbI~hMR!!@cUyCmM3(y31?$Qz~W z`h3}|v#qAI?0#p2-d*^zE3qJXAyrPv8Y4M=qL;VwD4Zpn*zVz5_h^OMfjS%N6>SzB zqr9Z-tDN|^@ozx-qP`mY9!IzCJPmG(R!i}UXhWxowH?>@*O9yxp4&g~SQ=-k4)O~a z$vm`^>>zb^jxXo$X?u2xteaEAS}LI#lje|{fY>*teY;mDFI@wj10jL$6O4cBY@!Q& zlw&!hVq(!@@MdS^PZGa1N^=lYg!M!upeqzg5`*TpM-ECN3(U<8%p@fe&NpMT$M+on(2B4z7pyLxPO;ksbA!N5oS33tuf zmj8Q%9q^#{XUc$Bbz520;hs1?w2@

tT2%Fs4fgJ9yMIWWK*Le_H*AQxd)^gDHEDR&s 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 L0LrL8GxYgi&WYme diff --git a/vendor/rails/railties/guides/images/i18n/demo_translation_missing.png b/vendor/rails/railties/guides/images/i18n/demo_translation_missing.png index 86a3121cc1452204d6d144486874182ec0fd160d..af9e2d04274578dc8ef9227f879ea5b06742e037 100644 GIT binary patch literal 13143 zcma)@WmHsOxbQ`47-HyV=nm-)=`InZyQI5Ix)CYq66x;l97?*oq`Tuq{olLR{q}x2 zF=sz{V*Q?H?+H^%x$ zLI0?vB)m0>#0<@L`aJe9`lg%tV$@DqIiox?)_wJ+W%oSoLU7D|l;;Ew!SDU`nLaVr z!);Xz->_7PAnM)09--JM8jLWi06eurGzO2^Q&Kgrjee%)b>VtDWX@OS8o}f7tpA1C zaYt@sJX@VUQ2hrXddp7(yDxqEVDH?QsD01*=-5ek7njU`9>Znvfp z7j?@c?KM~4XoVknzt^L+B(@wa+O{um%iWzj;z&-oZtji{cPw^83qJ**;-Zsj7t_#* zeEy8L{~h*zlN@BQQ92pK!o)(kguYuT@O$9y*4xdaA@?Qg%PArr(1pAxVl?Y=iD!xc zpZWSZ#6Dq1EopNp?vC+W`wCJ}ecCn)>Qew*R$(E+uIyNLP6K-1JrzGUA26kmEv?Ue z`&ZxwhNSW?RR_37hF~gU%^UnmdLU=&63$ z)9&=h6>WbT_G+9I(0-v1>2nHYHzLpdY`|LOdief2nN0*&_w2mS|D-aa*WJ*T_dJfv z(8d!3w8wEn%c`tdfUJkrFU{^ ztc3MX_MD~)P4Ldhyk2J)*j_IOx?85{y{6c~W7M_`JsU0O<{h2wWj*)pZDBgT_|Ah4 z_NF(Uw?KS`biv^r84edptH8r!YG&`yZzlDC@-i>N`qnerWAKr;fl1XR@?n$bWnPl9 z3X7ooRi+#DQ~aIV+?9Hr8`_QzTMwNatV`TIa2~GR-Yx2>3je`xPRM_5zNBr`7Y|y89JBnpQ#>V-ap?jNrwSQO9x!F%j z!#!t^rb@YMcYSe_jdIZyANJ)GZx1T(Bcg&}W}0f4?=t&}2jmEHa1 zZu(NhMUE)CzA(j-u!pmr7(iCn+zvj%dbvGY9@T*1bfFI0A*9IPyDQM{1)XE>Y=gF@ zoB6h~3pa3epZ1YBnN(BC8)-X>CEuB{rPWRz5A)7Hm(o`|GV%NfT3cDZ3y2BwGRC(*1)E-j zMh;2&JTSj$5-n+zucqUD!`CUbQ|u6pMcHInNDcPUC1jt4?e6{r)i{J)^%MR3sMpwG z62Xt3hAu0}&o*@CEhYs^-kJ^lCo41|_CH^0pg(|6W|y#l2|^KZ-tTOgddf{a4XkY5 zm)0Q3`0~KH-F00sfet<2={4+rX^gon%%)Z|DX>WVo;fOvQ)6<6lWzLGXK=81t@ucVZPGp(j^&Q0cNwr=| z9PO;Yl$LG&)^g==(pb7XIkM@CfJT*qoQX3=%fmy;s!IZLXU_EI+;3iXc4_9DXQ`I7 zbB3W>XY=!mw{+EK<>g=6A0Nc!OmrT1gnBpb$n`dok3#Gp!V4LqH6L&1LpmO>+$3-8L`?)A^u2vE+K%yv*LBU7W4ryuiHhLW6zIR^2x1+6_;BJ)Cy*yeUSS z-8jw1``&(u&`{Y#!uQJ+IcPGQdcIHvaC6y%Ix~4f9orFyhv=?Kxr9Q9Sq4FdTc5UZ zEhL~gMA+=cuJr8LP)*p?pSyiS^H2?L%lw811P#Q?+p%SA2@pxCKAtnY8{p5gyZE|p zfnAqH_KAITa=M@8ae)L0_YjoajL9k}^Z|NEj{5ssB3TQM1>*tm<$|8!5L zEz~vZuoP;v>K)GYVv7i5FiR|b0htCjZtg`>@ITGCK<2|rh9Mfs@CdBy!(r+Zs7bLr z#b#%{o-ciid@+C!16PJVm6*{^{k*$#o*6`6qY*7`LuqSRNOx%`mcN{p37*$AJ^$qm z2<;{F2p6)tsvY5IW~mjWeytcXr-w|1d?W0bi?$n0j2dkVo*s@20gvOOVwXYbKH~Mt zK8I$@o6L>bghc{PbzN$e@ZC=-u3sd%oy$;iyZnrVe7cd&E!^TWL!z-Xc3F_dn9KL? zG8GIu-P$>G)wTS%&i#;$yfn;Y73r#NH+H^BuB+zuzHdZ&27(II$A)yM0^*4fU4k>= zukAvHjSY4E5JpO7S(GOH@LgimTcFN)FNp~O;;3wx&lz7m-87JBTqwMy*jDMvGHRf+RtgF^5&MB7YX}tHiP(ZHXyXx0}pL^y7Fz>?HJ1*`I4*ts{uX1h!(pbV5b>`|8Ge-*d>P z)63?Evsq`Y(|mg%;Zlmtb}-}jz8ZGlC*s5Vbe8h9+Qt5EB?lm!B3|?C8w|Mh z1dyQk?fbRnDA!FrOO&0EQ0E1Di-Og~mHhZ9z>$mX*h>N8v_z98334l%#WEj0lW$Ar8T?z$ZWJzs`P#|GLVmr zS0h4PwNs)%G%PsbPZJ!f9rW$vkJN7>aoY*36#z$i_x z5PEKf>t}SJ4B6V^Dsgo#G5r3JecOVCTi8{Pt2bz=bt!3D^1;=$1~x>)){{1v?%>yt zT}@#ZXlUla*5q|k<$zP_g55$%aQz{<9+Td0{%S=Al z!-hevr2_w@^+en_y$QM=IS~PT-I>-skc=D3LN4iFTr6Qt4xd1zkz$+yKvvBzlL(=b zt;{1`Z{YHHLnqUhyWUIjXgN)P`n(##GSqC%2Ii{g?A`$DXDEJi7(N{^HdQ`ICQQY| zhNw_4ebTi=9UAZ@&ie`&6N%vZ)WxU3#WvPf*z3ndX1mI%eX-?fH_7#7rDlH-9Hb(; zE@+OrPd7w*a}hwmhKjC7yp!|*HEefEAJWjoD^*my{&QMHS1Y0^7;eVyA&hQK;>2ed z`i-&R>jO={{xe=Q=W87v(1?vqiPZo$KGHR6zJK62Hmrw-Ss(RsI!b9^Xn2uPW`?M z)0pu<`MlLK4qH_<(LUaylvz=KA~8#*&`NdfktZgYHBHd0jE$?&{Y*sKT7U|sV)Xa# zXxsbQ^e3$LHJ_4m3o#XMm1eJP6Q9V~7+YiOfk3s%iKPu?W=fPG=3wH(6+gDT>7Cns zV_a9Z)8Dq@1*zCn+U@T9N@+r#D1Y z*lz$t)UMK6i#F`3Nxwvg$0+qyFsNm-+L=145DWO=mWFZigqguz{%3vY#$Jd^cXmR1 zsYpMV9-6Z4I3-SU)iw`T1eovdL^=rozP=3G0G5hYG^Y~uc8t{x6N?)bElK4Qt?Jx51O|BcdVk=2rl zlv;f-iAp?UPQmB3k}X3NQX{bD0S`ovcH#I}0wyBRX-OZ8&#WfZWBnQQh0<0;*;1?- z?c^u=;S2D4CT8fpX>l9P`+Jys>tRulh3x^xCpCQ@uvbvm5OUov(Qg(&)8NA7$iiPv0JiF zuS`~-0v#V~p}jUiNFr;^Dt$|D z&9>Vo1Vv8pv=8jr%vN;fXb2sZU|2aC?77a1_w~(@Jd2#NFXY(Ojm>0(N#Tx%~gx>8X z)hgbySSc+tO79$!>~G5qvQ2*`g1_+&iyF&M7WlHzHu!oUgSeX_9gOPtH`_APpK{6Vn<*F_9&)^Y*1EmSe{1l(vp0LkSqIYA0c(wwfs*;)gH}&Ou|2;(ul+`Fg+0h)1BrakUGpT7 zQG6UyU1LWSx-H`idPVtdNutNHHk@|iWjJv^oT<2wx@Wtf##vk4Q->{cmgt?dfK}pS ztYfr@$*J+kc@=Qi%4%Hf?k9f)UtC}u!GVGO$WBOx_&^BH8H5Kjv#2ZW0E`y>Ff*37jY%u$UA3)E$_L<@_BP!y`GK2PS@0LO6^YUeekLI5>b@YvB96+`eO#QS9N!K zC0$ts2#`+};C7VN&FT>{9+`obW-VuUK6-DT=TDKIUFGF0zxL1A^d5E+h8Gnv?~SUL zSSxK5a_NcOd+r>h^y~FCNKWG15gDqzg5h4uMa!c5Nat_YNS2M?+|t9scP4n`A^D_cP6s zw4I=Dj=U?xJy}9e7?z`D9f&Nxl|3E%rn~9P3780?4w}>KKT32WVHJBf9A!fj!u0h0 zYw%5wy?)c5%Cc6td85`)VO$kH+>T?E$Kp^?QhJl{CXXy_z1d-5PZx~Z*8`DrGhmWF z-FJp$j+=ri>e|4)SXkVj-0%f4sNeHC(a_P;i(}czrd^r&2Gp&|2I_3btmoc$kFD|x z>#C^6_&>6y@v6a|kXEnrxcNiuv0piP_fe6>JPSxFde4&IFT01VraGIw=fpFbu*d8? zSMOFD~g$nGb5YDo{%x~NA2A4%2WMxd@gSUg+zM*vWdWXlgGfEzzCdx%Kf8uD1lS& z?mC}WLxNlyL~0+;FPrq4d1i*Kmp&-mcQ$_yY3NQYPY{>Wkc9hM`_%oTJ7m6ga>tUP zq?EyVs1sLE=}T3ddjV@Qgx@NZ>w;?dwN-X2YEabqyL!8{xMwN;3jk3qt0|I8P!OQR znRDtyy8!oy2>^4#Xv@RytEWJH(Jf%An2Lfuc@|~q(1~HS#%Ou_mUKF!b6)1cPhaIX z>7&UsNNVXGS!HWaE}FWyHRGPQ&I6_-2`6bRo_0?w_Oj)Or~cn%NR2 z10~4gk}J{QF+IWC+|9Pz3tZsd_I{o*;4{=0n3~mZIN(`YTbvV2%LWh&T?_13d&3Oz zwnoewxf>k)7@V4`VlMK=kzxvwYp|(lI)>wcm{)Iuxv)b@^7wTQ%n{C{Z>6^+a?|;T zg7_EOO(|tPS=jC%Tda z^i9H@djUn>6#Xd}H5k@% zw0VNBU3-Iq_fnOG-HQRAPBz=Q@QHnG_>2%fX~_Q3oF}XXCtJW3T@xMJ;5W9*kdiw6 zM#hdqSe!YUjld|*bQ~cdCk7XY&~Hxxd~LvYfx9?2zbOjd1&Rry!U%J-W7>FLACfA+ z9-|VK28xkc74J*rkO2Hi2uWV02EV)>+HHOOn*Hae9v$HLIL52oIxej{=Bq>?F8k$c z<6k&bv|HiNX+b(k%&QT9)PUfO*YN;qCRn^bR{i<^Y;&~0uz!q(di;@vSszydEJo`6 z&SU>wq-4VE+(M#4I-qC&d*V~Yr7AF~8#U>tO`1UgUBIaEOG=jzfDUgkxuNAZw7hyq zy$w=FxRvKD+1I=$!nlt(+}^~AZLPBVFyzuYj@gD;f`DM5KlAA=;vIlbd4x#{xwI- zge@K`C#y?W>!5#PYZ0S(^QdsHcLBecHgMJF-Hrmn(KkY*UdhvKoX-QaSQbR0eYzo` zcU+xuGeoGLn2WiSSR6D4D+y1+YrLlL)rlk>3VAeT)ftnrB$_KqGegCgsOf_yJD#28 zwej-OOArd&Y)8m_CA#&mNU-PAy zCKNnlqy{zs8xFy>oB6LVdVEAmqHM%;jVEAZB4QZy*|28X9XUzYLb+UmF)1D2?BabG z?YBBb5w-0g4or($**mGjuQ5ym*&)c{x7dDPiq3LCGsd4xNh1Zj=Mxx5_;j_oocYpgbbO>Y!+`)kI5Pi; zurRwhp8+M*Pk!0z+f~^#G7(3oB&wRU4K4y5p+D+EpKUu?G|7LUeud^2+W&)u#RHho}rjbbX?TdO7ypX~_{bE;vIC{uy9Zf6Y!1 z;wUSg2rgowDmf}?Z5$Ke^AVng&CF0XatrO+_HvR%5%43uK@`hdNSj(&0Q;x zm+>S-$a?va$^WdQDB8BM{ML?GvyP8$DK2~pg;clei-==r$y7{WJ3`FLLQ{xbO#FzA z)};Q(P;ys2%Y~k#vKsHVxVYGq*nl6BeGO_`MbK`RaYuu=!?=rr9F6ddRg`6NO)ax3 z`xCR3zczWZvZ25<;!~2Gpi^5)H&c&_?rC0sHEgZK%}skW&YXQ8!6xdEQG=4%d7eeT z>xW;k`j^a|5*oYc^VCk?*(=R3=R-AO<(_Q1zz1;$E23&k+H%UOq|VE-E+-E=A!NfK z*t)VcMiB z7>N?r=)a#`9C7kis+@za(IK5p;3~eKJB7?Rmu~vR z5N=N7RUuDkIgbZWp`816<9d=Nio;C@%I^K0yN*AjpUF6bc3(?q-qC28>>Xe|#22%T z+BXI6q?RDLNMV}g=@}r(SziAX8*vEx|4P~ZVX@0JIF>D~A zfmD6Q5@H(hWWeIT$Jfxfp&Te*!&dO{%a+?0fz_&Ls6X0zvBCs`eK?m1niQ9N_y8g8 zQMjKE852I~YVB+r0*}fwV^hqpGIDs#3#^$D``f0NxRUoZ6fE)~(O#8R&1(YrcFkYx za!xv&jc(?WPCJiTn?E_YT6JEF_6pci0A2MRI;4g*E8c(vZ%It$iNXeD>((Z&ZvAg=u14g=xt9zEm66I&j_!Q!zK7EA1(UpuSz+mVoPGh_%ED`*N5Rg*-`rQ# zLQ-wpqp4LKm5nE5V<)k}Jqcl!&HZ##D^YHII^AZrIsu|bGZiUz<`9|4^eYmwjq9%Q zK?P0tZhgnJxD>$Rbm4p|xJS0Rxh#?|F2-h1*!IwT+{!agp=-e3Y~xCuz}bSYu&BJB zR%<$~1%{ldN5B2$O5L>jgBnpYri$h=ES8Nr>f&(|6%)GA;ocSECr+FloU}pjo>Hkj zrIUjS`o*S+;?w0_V&}+3Q3Z?O1~R+P<@rOyZt%9PMU4$VJ~A&qSs5(v<`EGr)Jvv2p#@ax3^MTJc zm92Qd%?w|GFtO70qBDAJ@(j}I z&+K$d!`Du0N6MU4q7J`TUE#LHU{DPBnN?x`94gCf;Yz!$FYzfcCRb8n&5N{t0Igna6SjEQb~+o(Uou)ww*q4WA12d3f8$|SC1M8H97qm>JrTBKs>lzLP7`OKI?`!@{na`5MBH7EM$ z+hG}gj^4~bX;klFHEoynWNVtGs30hn&Ua#w3PW8HYypM%@33no;z;f(%Bm`!f($mdT|<|N8uD#+QUkRX8yV~@7JBn9^e#R2F^E-{p^5Pr|JJSUL& z%HVtS!T&hC3yttUdPh$grg|MBMk@M;%l|=s6H=?>UrHBo{EPV9#x#FWo`u`v|De77 zUeK#?Kv&`4$^gLnXRKIK*3I9+%6tFz_U5dqua@mBXa;g$_aubE?f>b1xAbTA(#AiN zucY&OqO;@R7SrX|wWR$qxyjBi$#dq0NOE>}9nY>xowV zv*R*NJ?|c`AJBm<3 zdc-*03p>ie(ifRsKUQ){oBzcwMb-iGDzy;U5*VlAos`h-R`*e324*}8kITE+Z_0C5 zopn$$yk_BpHi5@N8^s&t!uQujG&}y5rzho;mG)O64D$`pi?FkL{=2SQn*(*V0JGJ6 zMUdHUSLK}&-ia%@@2X3Q4i9MkBBQ@A9k>8Aoq?yJCPT3mBNm5I?$&3ih%v4u?Y zNH0zc2IP9U2-ehEZ#&(}>GZbclR1QoKf}!~W>3~Jd@%Lj8!6dH%Nmj0o*KRw@5!^z zOXzQ8v`0(p#aL&L{nb?queVA;*#9#Lcw^jCIB!q*si$rC2PMED&@(5lA@Y(!q1$(s z!a>tzS^X=SwruIv3B#Jgc5m;czb+$_U}EE`WAqYxo0Ob9Bwoze&oB}nDaS-BK~6|*tm-h& zd1wF8^kFxO=-4$wPauh~b9clmEeYLdFf~P1?%f(?2PN?AbAw!Ix=i^n>W1tv!#3;~ zrNsA1nbAqt4{9#sX7;g+N7PI~kl~6(JqqhZ&R^?u(gJ+l=2H&8?eT?~e#0iD)qQ|i zEMB?nRWS0=vk6~*a3fg%mc2&Y2AC-zN3YhD3(*)fM<0&T9?R(XaM7KfW`>FR*~2R^ z0e!*&!Qw(x`LlA(V5~JLjFV5<%`YdeDlnw#8|`w544=$jT1F$q6VqNk|x-3L03--7naI$`)(2Z zk@H>lVulJ@2sL@y!peEe6DEDB2_}XXbV);k2(hM=`O#yC{w2Klz_zj}d%V&rxv-th zm~CL>=ma(w=IFsZPu-d8Pa@tDA?nvRZpDg5J8hT~_+m-0FzN>A?gA$o7zQyXHm6P+ z4WE=7G>JuPeafpsBrT2e73|iOSQvRxY>5@%#M*HzsyOJ3$K@2457w?i{hh&O@U1=F zD)i?smY6;`odYG|PTTpehU(0mBaeYV%Mo(+1&@69Ln+8P4yZ=#H&19x2fPm}1VI%* zrvFYT8p1$@c<_+EJIj!8NDKmq>V3WHMimykvfkV&A_;_B#Hc~x5jr;TP!8}pEU2tR zGeDg9nwS#KWr}>_UvkJ+(*Eh-5u!SKmNzOEw&K1JZC{$v93{o)G?RFglpM?vx+0Lg z>)A5HKdp;}K6nBG`&kz1%8SBoD_9B6Vl7-w(Xb1kD_r#KK|g-|d~b2J z!+uaQ(@Eb&1q9X3B=9%AAJ}8zyul^vUn>|c8|u>V!NREIk4<25rcEqe4qnz;>5x?- zr!q`CfSo|;)N$N3e15$9&gV*HT6MxDe|dtYF^}&IkQmRmR#ncQIJObE(bsbQ;G6S$ zBXs$bkIs)P1&P$G*t53XhhLl+bq&)Sr)tcYMP|-XYh0{K!6tl9vrhz-&-KjwSL+5_ zdDQ;CJ!;;U-bX*p1w8S&T*)^!HuhEs+))c)O_(vCRJ(?#58;3L(q6ANHK*k07-4M*{cDPEA z9DAxjCI5o*&$_gyE6M8@+K=_-usV2rhGyYTM7GXKrXVzpIV#2!JrzO2ehFa=HX`WO zROz-!zjj%-xyDBpUtKvu87iO&Aa`uZUAC;=zl!)rMw?-z^m_r%Hu%ykjae^sGNVD#rv^lEl7 z>~i+hm3|9XklLO=H(EH3k>4I-!0;Ovb7YCNK-tG-$}I?gWm+rbeA8K%BSq)05=cp^ zpVRxB8$NAf*RCKsvnT0h?b21NFx*JmvHY?yc+1Km`5j?*Vx#9`&ZiRz(Kewzw85B( zeA@0d*ZXIDLKi)cs{-H4pIj!A-nfSwJ*8v3n6?Na9VnPZ>V zo=)?s{ld{=583hb+{0om+L?z7FNA5Ju@*eggb$}XnOj7%!cJj2scim(oCQtRgVFK7vA=MJ$e_7^FRXk80U**@Ql)nDOR&Iat z{}@=ef&3L`d;Wj8`9Bm^Hs1^T<5I{!(X{_-_&>G|^7*GfgzAsGfc*aj_Dj#dEKYOT zMf4{ilz(0Izux{b*%SB^ga~HJU&j8=snGvLZBfNP^#J!j(bri1ur|zJ1v#?g|D*Tc zo|`>c{`E392jHa@= z@IA=>UF| z@_%F%=gV&dbw@?;;*TF%M8dg6uUfdy5hq)nkI)xiNxtxM3L7 z3N`pq*rJ`Dm?IQ)ubE&)Z;7p7lN3&Uiw+}vDIN_Lu$YxnCyx75Z0+TqN|11Hs7^_p z_G9nmE4|vkPl>HC!#pIa#|8R4L?;mTw@PgMc|fgVr6}m#MbWwiKNQELPWRud8#P=^J&UF&VP6~f2v#MNd+lZPuo{d=MV0CKq`-1pKT{!5Sia( Z2v1HUoiGc%{!`|Sw77y;nTWps{{w6g8N>hp literal 34373 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 diff --git a/vendor/rails/railties/guides/images/i18n/demo_untranslated.png b/vendor/rails/railties/guides/images/i18n/demo_untranslated.png index e6717fb7d17edfd6c6f63a63884fa0cdf813bce2..3603f43463802610c9b54cd6cfce3c368d1e566b 100644 GIT binary patch literal 11925 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ℑ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 diff --git a/vendor/rails/railties/guides/rails_guides.rb b/vendor/rails/railties/guides/rails_guides.rb index 6da7de89..725f4cd8 100644 --- a/vendor/rails/railties/guides/rails_guides.rb +++ b/vendor/rails/railties/guides/rails_guides.rb @@ -22,6 +22,7 @@ module RailsGuides autoload :Indexer, "rails_guides/indexer" autoload :Helpers, "rails_guides/helpers" autoload :TextileExtensions, "rails_guides/textile_extensions" + autoload :Levenshtein, "rails_guides/levenshtein" end RedCloth.send(:include, RailsGuides::TextileExtensions) diff --git a/vendor/rails/railties/guides/rails_guides/generator.rb b/vendor/rails/railties/guides/rails_guides/generator.rb index 18fdb818..8e69af5b 100644 --- a/vendor/rails/railties/guides/rails_guides/generator.rb +++ b/vendor/rails/railties/guides/rails_guides/generator.rb @@ -109,8 +109,8 @@ module RailsGuides end def textile(body) - # If the issue with nontextile is fixed just remove the wrapper. - with_workaround_for_nontextile(body) do |body| + # If the issue with notextile is fixed just remove the wrapper. + with_workaround_for_notextile(body) do |body| t = RedCloth.new(body) t.hard_breaks = false t.to_html(:notestuff, :plusplus, :code, :tip) @@ -120,33 +120,51 @@ module RailsGuides # For some reason the notextile tag does not always turn off textile. See # LH ticket of the security guide (#7). As a temporary workaround we deal # with code blocks by hand. - def with_workaround_for_nontextile(body) + def with_workaround_for_notextile(body) code_blocks = [] body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain)>(.*?)}m) do |m| es = ERB::Util.h($2) css_class = ['erb', 'shell'].include?($1) ? 'html' : $1 code_blocks << %{

} - "dirty_workaround_for_nontextile_#{code_blocks.size - 1}" + "\ndirty_workaround_for_notextile_#{code_blocks.size - 1}\n" end body = yield body - body.gsub(%r{

dirty_workaround_for_nontextile_(\d+)

}) do |_| + body.gsub(%r{

dirty_workaround_for_notextile_(\d+)

}) do |_| code_blocks[$1.to_i] end end def warn_about_broken_links(html) + anchors = extract_anchors(html) + check_fragment_identifiers(html, anchors) + end + + def extract_anchors(html) # Textile generates headers with IDs computed from titles. - anchors = Set.new(html.scan(/ Levenshtein.distance(fragment_identifier, b) + } + puts "*** BROKEN LINK: ##{fragment_identifier}, perhaps you meant ##{guess}." end end end diff --git a/vendor/rails/railties/guides/rails_guides/indexer.rb b/vendor/rails/railties/guides/rails_guides/indexer.rb index 7cb254d0..5b5ad3fe 100644 --- a/vendor/rails/railties/guides/rails_guides/indexer.rb +++ b/vendor/rails/railties/guides/rails_guides/indexer.rb @@ -29,7 +29,7 @@ module RailsGuides return level_hash elsif level == current_level index = counters.join(".") - bookmark = '#' + title.gsub(/[^a-z0-9\-_]+/i, '').underscore.dasherize + bookmark = '#' + title.strip.downcase.gsub(/\s+|_/, '-').delete('^a-z0-9-') raise "Parsing Fail" unless @result.sub!(matched, "h#{level}(#{bookmark}). #{index}#{title}") diff --git a/vendor/rails/railties/guides/rails_guides/levenshtein.rb b/vendor/rails/railties/guides/rails_guides/levenshtein.rb new file mode 100644 index 00000000..02e35f60 --- /dev/null +++ b/vendor/rails/railties/guides/rails_guides/levenshtein.rb @@ -0,0 +1,112 @@ +# +# Levenshtein distance algorithm implementation for Ruby, with UTF-8 support +# +# Author:: Paul BATTLEY (pbattley @ gmail.com) +# Version:: 1.3 +# Date:: 2005-04-19 +# +# == About +# +# The Levenshtein distance is a measure of how similar two strings s and t are, +# calculated as the number of deletions/insertions/substitutions needed to +# transform s into t. The greater the distance, the more the strings differ. +# +# The Levenshtein distance is also sometimes referred to as the +# easier-to-pronounce-and-spell 'edit distance'. +# +# == Revision history +# +# * 2005-05-19 1.3 Repairing an oversight, distance can now be called via +# Levenshtein.distance(s, t) +# * 2005-05-04 1.2 Now uses just one 1-dimensional array. I think this is as +# far as optimisation can go. +# * 2005-05-04 1.1 Now storing only the current and previous rows of the matrix +# instead of the whole lot. +# +# == Licence +# +# Copyright (c) 2005 Paul Battley +# +# Usage of the works is permitted provided that this instrument is retained +# with the works, so that any entity that uses the works is notified of this +# instrument. +# +# DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. +# + +module Levenshtein + + # + # Calculate the Levenshtein distance between two strings +str1+ and +str2+. + # +str1+ and +str2+ should be ASCII or UTF-8. + # + def distance(str1, str2) + s = str1.unpack('U*') + t = str2.unpack('U*') + n = s.length + m = t.length + return m if (0 == n) + return n if (0 == m) + + d = (0..m).to_a + x = nil + + (0...n).each do |i| + e = i+1 + (0...m).each do |j| + cost = (s[i] == t[j]) ? 0 : 1 + x = [ + d[j+1] + 1, # insertion + e + 1, # deletion + d[j] + cost # substitution + ].min + d[j] = e + e = x + end + d[m] = x + end + + return x + end + + extend self +end + +if (__FILE__ == $0) + require 'test/unit' + + class LevenshteinTest < Test::Unit::TestCase + include Levenshtein + + EXPECTED = [ + # Easy ones + ['test', 'test', 0], + ['test', 'tent', 1], + ['gumbo', 'gambol', 2], + ['kitten', 'sitting', 3], + # Empty strings + ['foo', '', 3], + ['', '', 0], + ['a', '', 1], + # UTF-8 + ["f\303\266o", 'foo', 1], + ["fran\303\247ais", 'francais', 1], + ["fran\303\247ais", "fran\303\246ais", 1], + ["\347\247\201\343\201\256\345\220\215\345\211\215\343\201\257"<< + "\343\203\235\343\203\274\343\203\253\343\201\247\343\201\231", + "\343\201\274\343\201\217\343\201\256\345\220\215\345\211\215\343\201"<< + "\257\343\203\235\343\203\274\343\203\253\343\201\247\343\201\231", + 2], # Japanese + # Edge cases + ['a', 'a', 0], + ['0123456789', 'abcdefghijklmnopqrstuvwxyz', 26] + ] + + def test_known_distances + EXPECTED.each do |a,b,x| + assert_equal(x, distance(a, b)) + assert_equal(x, distance(b, a)) + end + end + end +end diff --git a/vendor/rails/railties/guides/source/2_3_release_notes.textile b/vendor/rails/railties/guides/source/2_3_release_notes.textile index c58cbc0b..cc2e2dc2 100644 --- a/vendor/rails/railties/guides/source/2_3_release_notes.textile +++ b/vendor/rails/railties/guides/source/2_3_release_notes.textile @@ -1,7 +1,5 @@ h2. Ruby on Rails 2.3 Release Notes -NOTE: These release notes refer to RC2 of Rails 2.3. This is a release candidate, and not the final version of Rails 2.3. It's intended to be a stable testing release, and we urge you to test your own applications and report any issues to the "Rails Lighthouse":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/overview. - Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the "list of commits":http://github.com/rails/rails/commits/master in the main Rails repository on GitHub or review the +CHANGELOG+ files for the individual Rails components. endprologue. @@ -22,24 +20,25 @@ Rails has now broken with its CGI past, and uses Rack everywhere. This required Here's a summary of the rack-related changes: * +script/server+ has been switched to use Rack, which means it supports any Rack compatible server. +script/server+ will also pick up a rackup configuration file if one exists. By default, it will look for a +config.ru+ file, but you can override this with the +-c+ switch. -* The FCGI handler goes through Rack -* +ActionController::Dispatcher+ maintains its own default middleware stack. Middlewares can be injected in, reordered, and removed. The stack is compiled into a chain on boot. You can configure the middleware stack in +environment.rb+ +* The FCGI handler goes through Rack. +* +ActionController::Dispatcher+ maintains its own default middleware stack. Middlewares can be injected in, reordered, and removed. The stack is compiled into a chain on boot. You can configure the middleware stack in +environment.rb+. * The +rake middleware+ task has been added to inspect the middleware stack. This is useful for debugging the order of the middleware stack. * The integration test runner has been modified to execute the entire middleware and application stack. This makes integration tests perfect for testing Rack middleware. * +ActionController::CGIHandler+ is a backwards compatible CGI wrapper around Rack. The +CGIHandler+ is meant to take an old CGI object and convert its environment information into a Rack compatible form. -* +CgiRequest+ and +CgiResponse+ have been removed +* +CgiRequest+ and +CgiResponse+ have been removed. * Session stores are now lazy loaded. If you never access the session object during a request, it will never attempt to load the session data (parse the cookie, load the data from memcache, or lookup an Active Record object). -* +CGI::Session::CookieStore+ has been replaced by +ActionController::Session::CookieStore+ -* +CGI::Session::MemCacheStore+ has been replaced by +ActionController::Session::MemCacheStore+ -* +CGI::Session::ActiveRecordStore+ has been replaced by +ActiveRecord::SessionStore+ -* You can still change your session store with +ActionController::Base.session_store = :active_record_store+ -* Default sessions options are still set with +ActionController::Base.session = { :key => "..." }+ -* The mutex that normally wraps your entire request has been moved into middleware, +ActionController::Lock+ +* You no longer need to use +CGI::Cookie.new+ in your tests for setting a cookie value. Assigning a +String+ value to request.cookies["foo"] now sets the cookie as expected. +* +CGI::Session::CookieStore+ has been replaced by +ActionController::Session::CookieStore+. +* +CGI::Session::MemCacheStore+ has been replaced by +ActionController::Session::MemCacheStore+. +* +CGI::Session::ActiveRecordStore+ has been replaced by +ActiveRecord::SessionStore+. +* You can still change your session store with +ActionController::Base.session_store = :active_record_store+. +* Default sessions options are still set with +ActionController::Base.session = { :key => "..." }+. +* The mutex that normally wraps your entire request has been moved into middleware, +ActionController::Lock+. * +ActionController::AbstractRequest+ and +ActionController::Request+ have been unified. The new +ActionController::Request+ inherits from +Rack::Request+. This affects access to +response.headers['type']+ in test requests. Use +response.content_type+ instead. * +ActiveRecord::QueryCache+ middleware is automatically inserted onto the middleware stack if +ActiveRecord+ has been loaded. This middleware sets up and flushes the per-request Active Record query cache. * The Rails router and controller classes follow the Rack spec. You can call a controller directly with +SomeController.call(env)+. The router stores the routing parameters in +rack.routing_args+. -* +ActionController::Request+ inherits from +Rack::Request+ -* Instead of +config.action_controller.session = { :session_key => 'foo', ...+ use +config.action_controller.session = { :key => 'foo', ...+ +* +ActionController::Request+ inherits from +Rack::Request+. +* Instead of +config.action_controller.session = { :session_key => 'foo', ...+ use +config.action_controller.session = { :key => 'foo', ...+. * Using the +ParamsParser+ middleware preprocesses any XML, JSON, or YAML requests so they can be read normally with any +Rack::Request+ object after it. h4. Renewed Support for Rails Engines @@ -50,7 +49,7 @@ h3. Documentation The "Ruby on Rails guides":http://guides.rubyonrails.org/ project has published several additional guides for Rails 2.3. In addition, a "separate site":http://guides.rails.info/ maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the "Rails wiki":http://newwiki.rubyonrails.org/ and early planning for a Rails Book. -* More Information: "Rails Documentation Projects":http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects +* More Information: "Rails Documentation Projects":http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects. h3. Ruby 1.9.1 Support @@ -140,19 +139,19 @@ end You can pass most of the +find+ options into +find_in_batches+. However, you cannot specify the order that records will be returned in (they will always be returned in ascending order of primary key, which must be an integer), or use the +:limit+ option. Instead, use the +:batch_size+ option, which defaults to 1000, to set the number of records that will be returned in each batch. -The new +each+ method provides a wrapper around +find_in_batches+ that returns individual records, with the find itself being done in batches (of 1000 by default): +The new +find_each+ method provides a wrapper around +find_in_batches+ that returns individual records, with the find itself being done in batches (of 1000 by default): -Customer.each do |customer| +Customer.find_each do |customer| customer.update_account_balance! end Note that you should only use this method for batch processing: for small numbers of records (less than 1000), you should just use the regular find methods with your own loop. -* More Information: - - "Rails 2.3: Batch Finding":http://afreshcup.com/2009/02/23/rails-23-batch-finding/ - - "What's New in Edge Rails: Batched Find":http://ryandaigle.com/articles/2009/2/23/what-s-new-in-edge-rails-batched-find +* More Information (at that point the convenience method was called just +each+): +** "Rails 2.3: Batch Finding":http://afreshcup.com/2009/02/23/rails-23-batch-finding/ +** "What's New in Edge Rails: Batched Find":http://ryandaigle.com/articles/2009/2/23/what-s-new-in-edge-rails-batched-find h4. Multiple Conditions for Callbacks @@ -175,18 +174,6 @@ developers = Developer.find(:all, :group => "salary", * Lead Contributor: "Emilio Tagua":http://github.com/miloops -h4. Hash Conditions for has_many relationships - -You can once again use a hash in conditions for a +has_many+ relationship: - - -has_many :orders, :conditions => {:status => 'confirmed'} - - -That worked in Rails 2.1, fails in Rails 2.2, and will now work again in Rails 2.3 (if you're dealing with this issue in Rails 2.2, you can use a string rather than a hash to specify conditions). - -* Lead Contributor: "Frederick Cheung":http://www.spacevatican.org/ - h4. Reconnecting MySQL Connections MySQL supports a reconnect flag in its connections - if set to true, then the client will try reconnecting to the server before giving up in case of a lost connection. You can now set +reconnect = true+ for your MySQL connections in +database.yml+ to get this behavior from a Rails application. The default is +false+, so the behavior of existing applications doesn't change. @@ -198,15 +185,17 @@ MySQL supports a reconnect flag in its connections - if set to true, then the cl h4. Other Active Record Changes -* An extra +AS+ was removed from the generated SQL for has_and_belongs_to_many preloading, making it work better for some databases. +* An extra +AS+ was removed from the generated SQL for +has_and_belongs_to_many+ preloading, making it work better for some databases. * +ActiveRecord::Base#new_record?+ now returns +false+ rather than +nil+ when confronted with an existing record. * A bug in quoting table names in some +has_many :through+ associations was fixed. * You can now specify a particular timestamp for +updated_at+ timestamps: +cust = Customer.create(:name => "ABC Industries", :updated_at => 1.day.ago)+ * Better error messages on failed +find_by_attribute!+ calls. * Active Record's +to_xml+ support gets just a little bit more flexible with the addition of a +:camelize+ option. -* A bug in canceling callbacks from +before_update+ or +before_create_ was fixed. +* A bug in canceling callbacks from +before_update+ or +before_create+ was fixed. * Rake tasks for testing databases via JDBC have been added. -* +validates_length_of+ will use a custom error message with the +:in+ or +:within+ options (if one is supplied) +* +validates_length_of+ will use a custom error message with the +:in+ or +:within+ options (if one is supplied). +* Counts on scoped selects now work properly, so you can do things like +Account.scoped(:select => "DISTINCT credit_limit").count+. +* +ActiveRecord::Base#invalid?+ now works as the opposite of +ActiveRecord::Base#valid?+. h3. Action Controller @@ -299,7 +288,7 @@ In some of the first fruits of the Rails-Merb team merger, Rails 2.3 includes so h4. Improved Caching Performance -Rails now keeps a per-request local cache of requests, cutting down on unnecessary reads and leading to better site performance. While this work was originally limited to +MemCacheStore+, it is available to any remote store than implements the required methods. +Rails now keeps a per-request local cache of read from the remote cache stores, cutting down on unnecessary reads and leading to better site performance. While this work was originally limited to +MemCacheStore+, it is available to any remote store than implements the required methods. * Lead Contributor: "Nahum Wild":http://www.motionstandingstill.com/ @@ -307,6 +296,8 @@ h4. Localized Views Rails can now provide localized views, depending on the locale that you have set. For example, suppose you have a +Posts+ controller with a +show+ action. By default, this will render +app/views/posts/show.html.erb+. But if you set +I18n.locale = :da+, it will render +app/views/posts/show.da.html.erb+. If the localized template isn't present, the undecorated version will be used. Rails also includes +I18n#available_locales+ and +I18n::SimpleBackend#available_locales+, which return an array of the translations that are available in the current Rails project. +In addition, you can use the same scheme to localize the rescue files in the +public+ directory: +public/500.da.html+ or +public/404.en.html+ work, for example. + h4. Partial Scoping for Translations A change to the translation API makes things easier and less repetitive to write key translations within partials. If you call +translate(".foo")+ from the +people/index.html.erb+ template, you'll actually be calling +I18n.translate("people.index.foo")+ If you don't prepend the key with a period, then the API doesn't scope, just as before. @@ -321,6 +312,9 @@ h4. Other Action Controller Changes * The +:only+ and +:except+ options for +map.resources+ are no longer inherited by nested resources. * The bundled memcached client has been updated to version 1.6.4.99. * The +expires_in+, +stale?+, and +fresh_when+ methods now accept a +:public+ option to make them work well with proxy caching. +* The +:requirements+ option now works properly with additional RESTful member routes. +* Shallow routes now properly respect namespaces. +* +polymorphic_url+ does a better job of handling objects with irregular plural names. h3. Action View @@ -439,6 +433,34 @@ returns +h4. Disabled Option Tags for Form Select Helpers + +The form select helpers (such as +select+ and +options_for_select+) now support a +:disabled+ option, which can take a single value or an array of values to be disabled in the resulting tags: + + +select(:post, :category, Post::CATEGORIES, :disabled => ‘private‘) + + +returns + + + + + +You can also use an anonymous function to determine at runtime which options from collections will be selected and/or disabled: + + +options_from_collection_for_select(@product.sizes, :name, :id, :disabled => lambda{|size| size.out_of_stock?}) + + +* Lead Contributor: "Tekin Suleyman":http://tekin.co.uk/ +* More Information: "New in rails 2.3 - disabled option tags and lambdas for selecting and disabling options from collections":http://tekin.co.uk/2009/03/new-in-rails-23-disabled-option-tags-and-lambdas-for-selecting-and-disabling-options-from-collections/ + h4. A Note About Template Loading Rails 2.3 includes the ability to enable or disable cached templates for any particular environment. Cached templates give you a speed boost because they don't check for a new template file when they're rendered - but they also mean that you can't replace a template "on the fly" without restarting the server. @@ -472,6 +494,17 @@ h4. Object#tap Backport +Object#tap+ is an addition to "Ruby 1.9":http://www.ruby-doc.org/core-1.9/classes/Object.html#M000309 and 1.8.7 that is similar to the +returning+ method that Rails has had for a while: it yields to a block, and then returns the object that was yielded. Rails now includes code to make this available under older versions of Ruby as well. +h4. Swappable Parsers for XMLmini + +The support for XML parsing in ActiveSupport has been made more flexible by allowing you to swap in different parsers. By default, it uses the standard REXML implementation, but you can easily specify the faster LibXML or Nokogiri implementations for your own applications, provided you have the appropriate gems installed: + + +XmlMini.backend = 'LibXML' + + +* Lead Contributor: "Bart ten Brinke":http://www.movesonrails.com/ +* Lead Contributor: "Aaron Patterson":http://tenderlovemaking.com/ + h4. Fractional seconds for TimeWithZone The +Time+ and +TimeWithZone+ classes include an +xmlschema+ method to return the time in an XML-friendly string. As of Rails 2.3, +TimeWithZone+ supports the same argument for specifying the number of digits in the fractional second part of the returned string that +Time+ does: @@ -494,6 +527,10 @@ h4. Other Active Support Changes * +ActiveSupport::OrderedHash+: now implements +each_key+ and +each_value+. * +ActiveSupport::MessageEncryptor+ provides a simple way to encrypt information for storage in an untrusted location (like cookies). * Active Support's +from_xml+ no longer depends on XmlSimple. Instead, Rails now includes its own XmlMini implementation, with just the functionality that it requires. This lets Rails dispense with the bundled copy of XmlSimple that it's been carting around. +* If you memoize a private method, the result will now be private. +* +String#parameterize+ accepts an optional separator: +"Quick Brown Fox".parameterize('_') => "quick_brown_fox"+. +* +number_to_phone+ accepts 7-digit phone numbers now. +* +ActiveSupport::Json.decode+ now handles +\u0000+ style escape sequences. h3. Railties @@ -532,6 +569,12 @@ Quite a bit of work was done to make sure that bits of Rails (and its dependenci You can also specify (by using the new +preload_frameworks+ option) whether the core libraries should be autoloaded at startup. This defaults to +false+ so that Rails autoloads itself piece-by-piece, but there are some circumstances where you still need to bring in everything at once - Passenger and JRuby both want to see all of Rails loaded together. +h4. rake gem Task Rewrite + +The internals of the various rake gem tasks have been substantially revised, to make the system work better for a variety of cases. The gem system now knows the difference between development and runtime dependencies, has a more robust unpacking system, gives better information when querying for the status of gems, and is less prone to "chicken and egg" dependency issues when you're bringing things up from scratch. There are also fixes for using gem commands under JRuby and for dependencies that try to bring in external copies of gems that are already vendored. + +* Lead Contributor: "David Dollar":http://www.workingwithrails.com/person/12240-david-dollar + h4. Other Railties Changes * The instructions for updating a CI server to build Rails have been updated and expanded. @@ -543,6 +586,8 @@ h4. Other Railties Changes * Rails Guides have been converted from AsciiDoc to Textile markup. * Scaffolded views and controllers have been cleaned up a bit. * +script/server+ now accepts a --path argument to mount a Rails application from a specific path. +* If any configured gems are missing, the gem rake tasks will skip loading much of the environment. This should solve many of the "chicken-and-egg" problems where rake gems:install couldn't run because gems were missing. +* Gems are now unpacked exactly once. This fixes issues with gems (hoe, for instance) which are packed with read-only permissions on the files. h3. Deprecated diff --git a/vendor/rails/railties/guides/source/action_controller_overview.textile b/vendor/rails/railties/guides/source/action_controller_overview.textile index 949a962b..054ca999 100644 --- a/vendor/rails/railties/guides/source/action_controller_overview.textile +++ b/vendor/rails/railties/guides/source/action_controller_overview.textile @@ -20,7 +20,7 @@ For most conventional RESTful applications, the controller will receive the requ 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. -NOTE: For more details on the routing process, see "Rails Routing from the Outside In":routing_outside_in.html. +NOTE: For more details on the routing process, see "Rails Routing from the Outside In":routing.html. h3. Methods and Actions @@ -83,7 +83,7 @@ class ClientsController < ActionController::Base end -h4. Hash and array parameters +h4. Hash and Array Parameters The +params+ hash is not limited to one-dimensional keys and values. It can contain arrays and (nested) hashes. To send an array of values, append an empty pair of square brackets "[]" to the key name: @@ -123,7 +123,7 @@ map.connect "/clients/:status", In this case, when a user opens the URL +/clients/active+, +params[:status]+ will be set to "active". When this route is used, +params[:foo]+ will also be set to "bar" just like it was passed in the query string. In the same way +params[:action]+ will contain "index". -h4. default_url_options +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: @@ -180,7 +180,7 @@ ActionController::Base.session = { NOTE: Changing the secret when using the CookieStore will invalidate all existing sessions. -h4. Accessing the session +h4. Accessing the Session In your controller you can access the session through the +session+ instance method. @@ -235,7 +235,7 @@ end To reset the entire session, use +reset_session+. -h4. The flash +h4. The Flash The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for storing error messages etc. It is accessed in much the same way as the session, like a hash. Let's use the act of logging out as an example. The controller can send a message which will be displayed to the user on the next request: @@ -288,7 +288,7 @@ class MainController < ApplicationController end -h5. flash.now +h5. +flash.now+ By default, adding values to the flash will make them available to the next request, but sometimes you may want to access those values in the same request. For example, if the +create+ action fails to save a resource and you render the +new+ template directly, that's not going to result in a new request, but you may still want to display a message using the flash. To do this, you can use +flash.now+ in the same way you use the normal +flash+: @@ -381,7 +381,7 @@ end Now, the +LoginsController+'s +new+ and +create+ actions will work as before without requiring the user to be logged in. The +:only+ option is used to only skip this filter for these actions, and there is also an +:except+ option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place. -h4. After filters and around filters +h4. After Filters and Around Filters In addition to before filters, you can run filters after an action has run or both before and after. The after filter is similar to the before filter, but because the action has already been run it has access to the response data that's about to be sent to the client. Obviously, after filters can not stop the action from running. @@ -403,7 +403,7 @@ private end -h4. Other ways to use filters +h4. Other Ways to Use Filters While the most common way to use filters is by creating private methods and using *_filter to add them, there are two other ways to do the same thing. @@ -517,7 +517,7 @@ h3. The Request and Response Objects In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The +request+ method contains an instance of +AbstractRequest+ and the +response+ method returns a response object representing what is going to be sent back to the client. -h4. The +request+ object +h4. The +request+ Object The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the "API documentation":http://api.rubyonrails.org/classes/ActionController/AbstractRequest.html. Among the properties that you can access on this object are: @@ -538,7 +538,7 @@ h5. +path_parameters+, +query_parameters+, and +request_parameters+ Rails collects all of the parameters sent along with the request in the +params+ hash, whether they are sent as part of the query string or the post body. The request object has three accessors that give you access to these parameters depending on where they came from. The +query_parameters+ hash contains parameters that were sent as part of the query string while the +request_parameters+ hash contains parameters sent as part of the post body. The +path_parameters+ hash contains parameters that were recognized by the routing as being part of the path leading to this particular controller and action. -h4. The response object +h4. The +response+ Object The response object is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values. @@ -550,7 +550,7 @@ The response object is not usually used directly, but is built up during the exe |charset|The character set being used for the response. Default is "utf-8".| |headers|Headers used for the response.| -h5. Setting custom headers +h5. Setting Custom Headers If you want to set custom headers for a response then +response.headers+ is the place to do it. The headers attribute is a hash which maps header names to their values, and Rails will set some of them automatically. If you want to add or change a header, just assign it to +response.headers+ this way: @@ -565,7 +565,7 @@ Rails comes with two built-in HTTP authentication mechanisms: * Basic Authentication * Digest Authentication -h4. HTTP basic authentication +h4. HTTP Basic Authentication HTTP basic authentication is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, +authenticate_or_request_with_http_basic+. @@ -587,7 +587,7 @@ end With this in place, you can create namespaced controllers that inherit from +AdminController+. The before filter will thus be run for all actions in those controllers, protecting them with HTTP basic authentication. -h4. HTTP digest authentication +h4. HTTP Digest Authentication HTTP digest authentication is superior to the basic authentication as it does not require the client to send an unencrypted password over the network (though HTTP basic authentication is safe over HTTPS). Using digest authentication with Rails is quite easy and only requires using one method, +authenticate_or_request_with_http_digest+. @@ -640,7 +640,7 @@ end The +download_pdf+ action in the example above will call a private method which actually generates the PDF document and returns it as a string. This string will then be streamed to the client as a file download and a filename will be suggested to the user. Sometimes when streaming files to the user, you may not want them to download the file. Take images, for example, which can be embedded into HTML pages. To tell the browser a file is not meant to be downloaded, you can set the +:disposition+ option to "inline". The opposite and default value for this option is "attachment". -h4. Sending files +h4. Sending Files If you want to send a file that already exists on disk, use the +send_file+ method. @@ -662,7 +662,7 @@ WARNING: Be careful when using data coming from the client (params, cookies, etc TIP: It is not recommended that you stream static files through Rails if you can instead keep them in a public folder on your web server. It is much more efficient to let the user download the file directly using Apache or another web server, keeping the request from unnecessarily going through the whole Rails stack. Although if you do need the request to go through Rails for some reason, you can set the +:x_sendfile+ option to true, and Rails will let the web server handle sending the file to the user, freeing up the Rails process to do other things. Note that your web server needs to support the +X-Sendfile+ header for this to work. -h4. RESTful downloads +h4. RESTful Downloads While +send_data+ works just fine, if you are creating a RESTful application having separate actions for file downloads is usually not necessary. In REST terminology, the PDF file from the example above can be considered just another representation of the client resource. Rails provides an easy and quite sleek way of doing "RESTful downloads". Here's how you can rewrite the example so that the PDF download is a part of the +show+ action, without any streaming: @@ -712,7 +712,7 @@ Most likely your application is going to contain bugs or otherwise throw an exce Rails' default exception handling displays a "500 Server Error" message for all exceptions. If the request was made locally, a nice traceback and some added information gets displayed so you can figure out what went wrong and deal with it. If the request was remote Rails will just display a simple "500 Server Error" message to the user, or a "404 Not Found" if there was a routing error or a record could not be found. Sometimes you might want to customize how these errors are caught and how they're displayed to the user. There are several levels of exception handling available in a Rails application: -h4. The default 500 and 404 templates +h4. The Default 500 and 404 Templates By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the +public+ folder, in +404.html+ and +500.html+ respectively. You can customize these files to add some extra information and layout, but remember that they are static; i.e. you can't use RHTML or layouts in them, just plain HTML. diff --git a/vendor/rails/railties/guides/source/action_mailer_basics.textile b/vendor/rails/railties/guides/source/action_mailer_basics.textile index 71398382..9476635a 100644 --- a/vendor/rails/railties/guides/source/action_mailer_basics.textile +++ b/vendor/rails/railties/guides/source/action_mailer_basics.textile @@ -12,9 +12,9 @@ h3. Sending Emails This section will provide a step-by-step guide to creating a mailer and its views. -h4. Walkthrough to generating a mailer +h4. Walkthrough to Generating a Mailer -h5. Create the mailer: +h5. Create the Mailer ./script/generate mailer UserMailer @@ -28,7 +28,7 @@ create test/unit/user_mailer_test.rb So we got the model, the fixtures, and the tests. -h5. Edit the model: +h5. Edit the Model +app/models/user_mailer.rb+ contains an empty mailer: @@ -60,7 +60,7 @@ Here is a quick explanation of the options presented in the preceding method. Fo The keys of the hash passed to +body+ become instance variables in the view. Thus, in our example the mailer view will have a +@user+ and a +@url+ instance variables available. -h5. Create a mailer view +h5. Create a Mailer View Create a file called +welcome_email.text.html.erb+ in +app/views/user_mailer/+. This will be the template used for the email, formatted in HTML: @@ -83,7 +83,7 @@ Create a file called +welcome_email.text.html.erb+ in +app/views/user_mailer/+. Had we wanted to send text-only emails, the file would have been called +welcome_email.text.plain.erb+. Rails sets the content type of the email to be the one in the filename. -h5. Wire it up so that the system sends the email when a user signs up +h5. Wire It Up So That the System Sends the Email When a User Signs Up There are three ways to achieve this. One is to send the email from the controller that sends the email, another is to put it in a +before_create+ callback in the user model, and the last one is to use an observer on the user model. Whether you use the second or third methods is up to you, but staying away from the first is recommended. Not because it's wrong, but because it keeps your controller clean, and keeps all logic related to the user model within the user model. This way, whichever way a user is created (from a web form, or from an API call, for example), we are guaranteed that the email will be sent. @@ -112,7 +112,7 @@ end Notice how we call +deliver_welcome_email+? In Action Mailer we send emails by calling +deliver_<method_name>+. In UserMailer, we defined a method called +welcome_email+, and so we deliver the email by calling +deliver_welcome_email+. The next section will go through how Action Mailer achieves this. -h4. Action Mailer and dynamic deliver_<method_name> methods +h4. Action Mailer and Dynamic +deliver_<method_name>+ methods So how does Action Mailer understand this +deliver_welcome_email+ call? If you read the documentation (http://api.rubyonrails.org/files/vendor/rails/actionmailer/README.html), you will find this in the "Sending Emails" section: @@ -135,7 +135,7 @@ end Hence, if the method name starts with +deliver_+ followed by any combination of lowercase letters or underscore, +method_missing+ calls +new+ on your mailer class (+UserMailer+ in our example above), sending the combination of lower case letters or underscore, along with the parameters. The resulting object is then sent the +deliver!+ method, which well... delivers it. -h4. Complete list of Action Mailer user-settable attributes +h4. Complete List of Action Mailer User-Settable Attributes |bcc| The BCC addresses of the email| |body| The body of the email. This is either a hash (in which case it specifies the variables to pass to the template when it is rendered), or a string, in which case it specifies the actual body of the message| @@ -184,7 +184,7 @@ end Just like with controller views, use +yield+ to render the view inside the layout. -h4. Generating URLs in Action Mailer views +h4. Generating URLs in Action Mailer Views URLs can be generated in mailer views using +url_for+ or named routes. Unlike controllers, the mailer instance doesn't have any context about the incoming request so you'll need to provide the +:host+, +:controller+, and +:action+: @@ -216,7 +216,7 @@ config.action_mailer.default_url_options = { :host => "example.com" } If you set a default +:host+ for your mailers you need to pass +:only_path => false+ to +url_for+. Otherwise it doesn't get included. -h4. Sending multipart emails +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.plain.erb+ and +welcome_email.text.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. @@ -240,7 +240,7 @@ class UserMailer < ActionMailer::Base end -h4. Sending emails with attachments +h4. Sending Emails with Attachments Attachments can be added by using the +attachment+ method: @@ -262,6 +262,38 @@ class UserMailer < ActionMailer::Base end +h4. Sending Multipart Emails with Attachments + +Once you use the +attachment+ method, ActionMailer will no longer automagically use the correct template based on the filename. You must declare which template you are using for each content type via the +part+ method. + +In the following example, there would be two template files, +welcome_email_html.erb+ and +welcome_email_plain.erb+ in the +app/views/user_mailer+ folder. + + +class UserMailer < ActionMailer::Base + def welcome_email(user) + recipients user.email_address + subject "New account information" + from "system@example.com" + content_type "multipart/alternative" + + part "text/html" do |p| + p.body = render_message("welcome_email_html", :message => "

HTML content

") + end + + part "text/plain" do |p| + p.body = render_message("welcome_email_plain", :message => "text content") + end + + attachment :content_type => "image/jpeg", + :body => File.read("an-image.jpg") + + attachment "application/pdf" do |a| + a.body = generate_your_pdf_here() + end + end +end +
+ h3. Receiving Emails Receiving and parsing emails with Action Mailer can be a rather complex endeavour. Before your email reaches your Rails app, you would have had to configure your system to somehow forward emails to your app, which needs to be listening for that. So, to receive emails in your Rails app you'll need: @@ -320,7 +352,7 @@ The following configuration options are best made in one of the environment file |default_implicit_parts_order|When a message is built implicitly (i.e. multiple parts are assembled from templates which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client and appear last in the mime encoded message. You can also pick a different order from inside a method with implicit_parts_order.| -h4. Example Action Mailer configuration +h4. Example Action Mailer Configuration An example would be: @@ -352,7 +384,7 @@ ActionMailer::Base.smtp_settings = { } -h4. Configure Action Mailer to recognize HAML templates +h4. Configure Action Mailer to Recognize HAML Templates In +config/environment.rb+, add the following line: @@ -386,3 +418,7 @@ end In the test we send the email and store the returned object in the +email+ variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain the what we expect. + +h3. Changelog + +"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/25 diff --git a/vendor/rails/railties/guides/source/active_record_basics.textile b/vendor/rails/railties/guides/source/active_record_basics.textile index ed3f6cdd..afff892f 100644 --- a/vendor/rails/railties/guides/source/active_record_basics.textile +++ b/vendor/rails/railties/guides/source/active_record_basics.textile @@ -29,7 +29,7 @@ h3. Object Relational Mapping The relation between databases and object-oriented software is called ORM, which is short for "Object Relational Mapping". The purpose of an ORM framework is to minimize the mismatch existent between relational databases and object-oriented software. In applications with a domain model, we have objects that represent both the state of the system and the behavior of the real world elements that were modeled through these objects. Since we need to store the system's state somehow, we can use relational databases, which are proven to be an excellent approach to data management. Usually this may become a very hard thing to do, since we need to create an object-oriented model of everything that lives in the database, from simple columns to complicated relations between different tables. Doing this kind of thing by hand is a tedious and error prone job. This is where an ORM framework comes in. -h3. ActiveRecord as an ORM framework +h3. ActiveRecord as an ORM Framework ActiveRecord gives us several mechanisms, being the most important ones the ability to: @@ -41,7 +41,7 @@ ActiveRecord gives us several mechanisms, being the most important ones the abil It's easy to see that the Rails Active Record implementation goes way beyond the basic description of the Active Record Pattern. -h3. Active Record inside the MVC model +h3. Active Record Inside the MVC Model Active Record plays the role of model inside the MVC structure followed by Rails applications. Since model objects should encapsulate both state and logic of your applications, it's ActiveRecord responsibility to deliver you the easiest possible way to recover this data from the database. @@ -81,7 +81,7 @@ There are also some optional column names that will create additional features t NOTE: While these column names are optional they are in fact reserved by ActiveRecord. Steer clear of reserved keywords unless you want the extra functionality. For example, "type" is a reserved keyword used to designate a table using Single Table Inheritance. If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling. -h3. Creating ActiveRecord models +h3. Creating ActiveRecord Models It's very easy to create ActiveRecord models. All you have to do is to subclass the ActiveRecord::Base class and you're good to go: @@ -107,7 +107,7 @@ p.name = "Some Book" puts p.name # "Some Book" -h3. Overriding the naming conventions +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. diff --git a/vendor/rails/railties/guides/source/active_record_querying.textile b/vendor/rails/railties/guides/source/active_record_querying.textile index 03e1b264..442521cb 100644 --- a/vendor/rails/railties/guides/source/active_record_querying.textile +++ b/vendor/rails/railties/guides/source/active_record_querying.textile @@ -54,7 +54,7 @@ end Active Record will perform queries on the database for you and is compatible with most database systems (MySQL, PostgreSQL and SQLite to name a few). Regardless of which database system you're using, the Active Record method format will always be the same. -h3. Retrieving objects from the database +h3. Retrieving Objects from the Database To retrieve objects from the database, Active Record provides a class method called +Model.find+. This method allows you to pass arguments into it to perform certain queries on your database without the need of writing raw SQL. @@ -65,11 +65,11 @@ Primary operation of Model.find(options) can be summarized as: * Instantiate the equivalent Ruby object of the appropriate model for every resulting row. * Run +after_find+ callbacks if any. -h4. Retrieving a single object +h4. Retrieving a Single Object Active Record lets you retrieve a single object using three different ways. -h5. Using a primary key +h5. Using a Primary Key Using Model.find(primary_key, options = nil), you can retrieve the object corresponding to the supplied _primary key_ and matching the supplied options (if any). For example: @@ -87,7 +87,7 @@ SELECT * FROM clients WHERE (clients.id = 10) Model.find(primary_key) will raise an +ActiveRecord::RecordNotFound+ exception if no matching record is found. -h5. Find first +h5. +first+ Model.first(options = nil) finds the first record matched by the supplied options. If no +options+ are supplied, the first matching record is returned. For example: @@ -106,7 +106,7 @@ SELECT * FROM clients LIMIT 1 NOTE: +Model.find(:first, options)+ is equivalent to +Model.first(options)+ -h5. Find last +h5. +last+ Model.last(options = nil) finds the last record matched by the supplied options. If no +options+ are supplied, the last matching record is returned. For example: @@ -126,9 +126,9 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1 NOTE: +Model.find(:last, options)+ is equivalent to +Model.last(options)+ -h4. Retrieving multiple objects +h4. Retrieving Multiple Objects -h5. Using multiple primary keys +h5. Using Multiple Primary Keys Model.find(array_of_primary_key, options = nil) also accepts an array of _primary keys_. An array of all the matching records for the supplied _primary keys_ is returned. For example: @@ -166,17 +166,85 @@ SELECT * FROM clients NOTE: +Model.find(:all, options)+ is equivalent to +Model.all(options)+ +h4. Retrieving Multiple Objects in Batches + +Sometimes you need to iterate over a large set of records. For example to send a newsletter to all users, to export some data, etc. + +The following may seem very straight forward at first: + + +# Very inefficient when users table has thousands of rows. +User.all.each do |user| + NewsLetter.weekly_deliver(user) +end + + +But if the total number of rows in the table is very large, the above approach may vary from being under performant to just plain impossible. + +This is because +User.all+ makes Active Record fetch _the entire table_, build a model object per row, and keep the entire array in the memory. Sometimes that is just too many objects and demands too much memory. + +h5. +find_each+ + +To efficiently iterate over a large table, Active Record provides a batch finder method called +find_each+: + + +User.find_each do |user| + NewsLetter.weekly_deliver(user) +end + + +*Configuring the batch size* + +Behind the scenes +find_each+ fetches rows in batches of +1000+ and yields them one by one. The size of the underlying batches is configurable via the +:batch_size+ option. + +To fetch +User+ records in batch size of +5000+: + + +User.find_each(:batch_size => 5000) do |user| + NewsLetter.weekly_deliver(user) +end + + +*Starting batch find from a specific primary key* + +Records are fetched in ascending order on the primary key, which must be an integer. The +:start+ option allows you to configure the first ID of the sequence if the lowest is not the one you need. This may be useful for example to be able to resume an interrupted batch process if it saves the last processed ID as a checkpoint. + +To send newsletters only to users with the primary key starting from +2000+: + + +User.find_each(:batch_size => 5000, :start => 2000) do |user| + NewsLetter.weekly_deliver(user) +end + + +*Additional options* + ++find_each+ accepts the same options as the regular +find+ method. However, +:order+ and +:limit+ are needed internally and hence not allowed to be passed explicitly. + +h5. +find_in_batches+ + +You can also work by chunks instead of row by row using +find_in_batches+. This method is analogous to +find_each+, but it yields arrays of models instead: + + +# Works in chunks of 1000 invoices at a time. +Invoice.find_in_batches(:include => :invoice_lines) do |invoices| + export.add_invoices(invoices) +end + + +The above will yield the supplied block with +1000+ invoices every time. + h3. Conditions -The +find+ method allows you to specify conditions to limit the records returned, representing the WHERE-part of the SQL statement. Conditions can either be specified as a string, array, or hash. +The +find+ method allows you to specify conditions to limit the records returned, representing the +WHERE+-part of the SQL statement. Conditions can either be specified as a string, array, or hash. -h4. Pure string conditions +h4. Pure String Conditions If you'd like to add conditions to your find, you could just specify them in there, just like +Client.first(:conditions => "orders_count = '2'")+. This will find all clients where the +orders_count+ field's value is 2. WARNING: Building your own conditions as pure strings can leave you vulnerable to SQL injection exploits. For example, +Client.first(:conditions => "name LIKE '%#{params[:name]}%'")+ is not safe. See the next section for the preferred way to handle conditions using an array. -h4. Array conditions +h4. Array Conditions Now what if that number could vary, say as a argument from somewhere, or perhaps from the user's level status somewhere? The find then becomes something like: @@ -208,11 +276,11 @@ Client.first(:conditions => "orders_count = #{params[:orders]}") is because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database *as-is*. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string. -TIP: For more information on the dangers of SQL injection, see the "Ruby on Rails Security Guide":../security.html#_sql_injection. +TIP: For more information on the dangers of SQL injection, see the "Ruby on Rails Security Guide":security.html#sql-injection. -h5. Placeholder conditions +h5. Placeholder Conditions -Similar to the +(?)+ replacement style of params, you can also specify keys/values hash in your Array conditions: +Similar to the +(?)+ replacement style of params, you can also specify keys/values hash in your array conditions: Client.all(:conditions => @@ -221,7 +289,7 @@ Client.all(:conditions => This makes for clearer readability if you have a large number of variable conditions. -h5. Range conditions +h5. 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: @@ -243,7 +311,7 @@ SELECT * FROM users WHERE (created_at IN '2008-12-27','2008-12-28','2008-12-29','2008-12-30','2008-12-31')) -h5. Time and Date conditions +h5. Time and Date Conditions Things can get *really* messy if you pass in Time objects as it will attempt to compare your field to *every second* in that range: @@ -280,15 +348,15 @@ Client.all(:conditions => ["created_at >= ? AND created_at <= ?", params[:start_date], params[:end_date]]) -Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash Conditions":hash-conditions section later on in the guide. +Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash Conditions":#hash-conditions section later on in the guide. -h4. Hash conditions +h4. Hash Conditions Active Record also allows you to pass in a 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: NOTE: Only equality, range and subset checking are possible with Hash conditions. -h5. Equality conditions +h5. Equality Conditions Client.all(:conditions => { :locked => true }) @@ -300,7 +368,7 @@ The field name does not have to be a symbol it can also be a string: Client.all(:conditions => { 'locked' => true }) -h5. Range conditions +h5. Range Conditions 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. @@ -314,9 +382,9 @@ This will find all clients created yesterday by using a +BETWEEN+ SQL statement: SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00') -This demonstrates a shorter syntax for the examples in "Array Conditions":#arrayconditions +This demonstrates a shorter syntax for the examples in "Array Conditions":#array-conditions -h5. Subset conditions +h5. Subset Conditions If you want to find records using the +IN+ expression you can pass an array to the conditions hash: @@ -330,7 +398,7 @@ This code will generate SQL like this: SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) -h3. Find options +h3. Find Options Apart from +:conditions+, +Model.find+ takes a variety of other options via the options hash for customizing the resulting record set. @@ -370,13 +438,13 @@ Or ordering by multiple fields: Client.all(:order => "orders_count ASC, created_at DESC") -h4. Selecting specific fields +h4. Selecting Specific Fields By default, Model.find selects all the fields from the result set using +select *+. To select only a subset of fields from the result set, you can specify the subset via +:select+ option on the +find+. -NOTE: If the +:select+ option is used, all the returning objects will be "read only":#readonlyobjects. +NOTE: If the +:select+ option is used, all the returning objects will be "read only":#readonly-objects.
@@ -470,7 +538,7 @@ SELECT * FROM orders GROUP BY date(created_at) HAVING created_at > '2009-01-15' This will return single order objects for each day, but only for the last month. -h4. Readonly objects +h4. Readonly Objects To explicitly disallow modification/destroyal of the matching records returned by +Model.find+, you could specify the +:readonly+ option as +true+ to the find call. @@ -488,7 +556,7 @@ client.locked = false client.save -h4. Locking records for update +h4. Locking Records for Update Locking is helpful for preventing the race conditions when updating records in the database and ensuring atomic updated. Active Record provides two locking mechanism: @@ -562,31 +630,31 @@ Item.transaction do end -h3. Joining tables +h3. Joining Tables Model.find provides a +:joins+ option for specifying +JOIN+ clauses on the resulting SQL. There multiple different ways to specify the +:joins+ option: -h4. Using a string SQL fragment +h4. Using a String SQL Fragment You can just supply the raw SQL specifying the +JOIN+ clause to the +:joins+ option. For example: -Client.all(:joins => 'LEFT OUTER JOIN addresses ON addresses.client_id = client.id') +Client.all(:joins => 'LEFT OUTER JOIN addresses ON addresses.client_id = clients.id') This will result in the following SQL: -SELECT clients.* FROM clients INNER JOIN addresses ON addresses.client_id = clients.id +SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id -h4. Using Array/Hash of named associations +h4. Using Array/Hash of Named Associations WARNING: This method only works with +INNER JOIN+,
-Active Record lets you use the names of the "associations":association_basics.html defined on the Model, as a shortcut for specifying the +:joins+ option. +Active Record lets you use the names of the "associations":association_basics.html defined on the model as a shortcut for specifying the +:joins+ option. For example, consider the following +Category+, +Post+, +Comments+ and +Guest+ models: @@ -613,7 +681,7 @@ end Now all of the following will produce the expected join queries using +INNER JOIN+: -h5. Joining a single association +h5. Joining a Single Association Category.all :joins => :posts @@ -626,7 +694,7 @@ SELECT categories.* FROM categories INNER JOIN posts ON posts.category_id = categories.id -h5. Joining multiple associations +h5. Joining Multiple Associations Post.all :joins => [:category, :comments] @@ -640,21 +708,21 @@ SELECT posts.* FROM posts INNER JOIN comments ON comments.post_id = posts.id -h5. Joining nested associations (single level) +h5. Joining Nested Associations (Single Level) Post.all :joins => {:comments => :guest} -h5. Joining nested associations (multiple level) +h5. Joining Nested Associations (Multiple Level) Category.all :joins => {:posts => [{:comments => :guest}, :tags]} -h4. Specifying conditions on the joined tables +h4. Specifying Conditions on the Joined Tables -You can specify conditions on the joined tables using the regular "Array":#arrayconditions and "String":#purestringconditions conditions. "Hash conditions":#hashconditions provides a special syntax for specifying conditions for the joined tables: +You can specify conditions on the joined tables using the regular "Array":#array-conditions and "String":#pure-string-conditions conditions. "Hash conditions":#hash-conditions provides a special syntax for specifying conditions for the joined tables: time_range = (Time.now.midnight - 1.day)..Time.now.midnight @@ -670,7 +738,7 @@ Client.all :joins => :orders, :conditions => {:orders => {:created_at => time_ra This will find all clients who have orders that were created yesterday, again using a +BETWEEN+ SQL expression. -h3. Eager loading associations +h3. Eager Loading Associations Eager loading is the mechanism for loading the associated records of the objects returned by +Model.find+ using as few queries as possible. @@ -710,11 +778,11 @@ SELECT addresses.* FROM addresses WHERE (addresses.client_id IN (1,2,3,4,5,6,7,8,9,10)) -h4. Eager loading multiple associations +h4. Eager Loading Multiple Associations -Active Record lets you eager load any possible number of associations with a single +Model.find+ call by using Array, Hash or a nested Hash of Array/Hash with +:include+ find option. +Active Record lets you eager load any possible number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +:include+ option. -h5. Array of multiple associations +h5. Array of Multiple Associations Post.all :include => [:category, :comments] @@ -722,7 +790,7 @@ Post.all :include => [:category, :comments] This loads all the posts and the associated category and comments for each post. -h5. Nested assocaitions hash +h5. Nested Associations Hash Category.find 1, :include => {:posts => [{:comments => :guest}, :tags]} @@ -730,17 +798,17 @@ Category.find 1, :include => {:posts => [{:comments => :guest}, :tags]} The above code finds the category with id 1 and eager loads all the posts associated with the found category. Additionally, it will also eager load every posts' tags and comments. Every comment's guest association will get eager loaded as well. -h4. Specifying conditions on eager loaded associations +h4. Specifying Conditions on Eager Loaded Associations -Even though Active Record lets you specify conditions on the eager loaded associations just like +:joins+, the recommended way is to use ":joins":#joiningtables instead. +Even though Active Record lets you specify conditions on the eager loaded associations just like +:joins+, the recommended way is to use ":joins":#joining-tables instead. -h3. Dynamic finders +h3. Dynamic Finders -For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your Client model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the Client model, you also get +find_by_locked+ and +find_all_by_locked+. +For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your +Client+ model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the +Client+ model, you also get +find_by_locked+ and +find_all_by_locked+. You can do +find_last_by_*+ methods too which will find the last record matching your argument. -You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an ActiveRecord::RecordNotFound error if they do not return any records, like +Client.find_by_name!("Ryan")+ +You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an +ActiveRecord::RecordNotFound+ error if they do not return any records, like +Client.find_by_name!("Ryan")+ If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields for example +Client.find_by_name_and_locked("Ryan", true)+. @@ -761,9 +829,9 @@ COMMIT client = Client.find_or_initialize_by_name('Ryan') -will either assign an existing client object with the name 'Ryan' to the client local variable, or initialize a new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it. +will either assign an existing client object with the name "Ryan" to the client local variable, or initialize a new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it. -h3. Finding By SQL +h3. Finding by SQL If you'd like to use your own SQL to find records in a table you can use +find_by_sql+. The +find_by_sql+ method will return an array of objects even the underlying query returns just a single record. For example you could run this query: @@ -775,7 +843,7 @@ Client.find_by_sql("SELECT * FROM clients +find_by_sql+ provides you with a simple way of making custom calls to the database and retrieving instantiated objects. -h3. select_all +h3. +select_all+ find_by_sql has a close relative called +connection#select_all+. +select_all+ will retrieve objects from the database using custom SQL just like +find_by_sql+ but will not instantiate them. Instead, you will get an array of hashes where each hash indicates a record. diff --git a/vendor/rails/railties/guides/source/activerecord_validations_callbacks.textile b/vendor/rails/railties/guides/source/activerecord_validations_callbacks.textile index 01e52bf0..5ae48842 100644 --- a/vendor/rails/railties/guides/source/activerecord_validations_callbacks.textile +++ b/vendor/rails/railties/guides/source/activerecord_validations_callbacks.textile @@ -16,7 +16,7 @@ endprologue. h3. The Object Lifecycle -During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this object lifecycle so that you can control your application and its data. +During the normal operation of a Rails application objects may be created, updated, and destroyed. Active Record provides hooks into this object lifecycle so that you can control your application and its data. Validations allow you to ensure that only valid data is stored in your database. Callbacks and observers allow you to trigger logic before or after an alteration of an object's state. @@ -26,18 +26,18 @@ Before you dive into the detail of validations in Rails, you should understand a h4. Why Use Validations? -Validations are used to ensure that only valid data is saved into your database. For example, it may be important to your application to ensure that every user provides a valid email address and mailing address +Validations are used to ensure that only valid data is saved into your database. For example, it may be important to your application to ensure that every user provides a valid email address and mailing address. There are several ways to validate data before it is saved into your database, including native database constraints, client-side validations, controller-level validations, and model-level validations. * Database constraints and/or stored procedures make the validation mechanisms database-dependent and can make testing and maintenance more difficult. However, if your database is used by other applications, it may be a good idea to use some constraints at the database level. Additionally, database-level validations can safely handle some things (such as uniqueness in heavily-used tables) that can be difficult to implement otherwise. -* Client-side validations can be useful, but are generally unreliable if used alone. If they are implemented using Javascript, they may be bypassed if Javascript is turned off in the user's browser. However, if combined with other techniques, client-side validation can be a convenient way to provide users with immediate feedback as they use your site. +* Client-side validations can be useful, but are generally unreliable if used alone. If they are implemented using JavaScript, they may be bypassed if JavaScript is turned off in the user's browser. However, if combined with other techniques, client-side validation can be a convenient way to provide users with immediate feedback as they use your site. * Controller-level validations can be tempting to use, but often become unwieldy and difficult to test and maintain. Whenever possible, it's a good idea to "keep your controllers skinny":http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model, as it will make your application a pleasure to work with in the long run. * Model-level validations are the best way to ensure that only valid data is saved into your database. They are database agnostic, cannot be bypassed by end users, and are convenient to test and maintain. Rails makes them easy to use, provides built-in helpers for common needs, and allows you to create your own validation methods as well. h4. When Does Validation Happen? -There are two kinds of Active Record objects: those that correspond to a row inside your database and those that do not. When you create a fresh object, using the +new+ method, that object does not belong to the database yet. Once you call +save+ upon that object it will be saved into the appropriate database table. Active Record uses the +new_record?+ instance method to determine whether an object is already in the database or not. Consider the following simple Active Record class: +There are two kinds of Active Record objects: those that correspond to a row inside your database and those that do not. When you create a fresh object, for example using the +new+ method, that object does not belong to the database yet. Once you call +save+ upon that object it will be saved into the appropriate database table. Active Record uses the +new_record?+ instance method to determine whether an object is already in the database or not. Consider the following simple Active Record class: class Person < ActiveRecord::Base @@ -57,11 +57,11 @@ We can see how it works by looking at some script/console output: => false
-Creating and saving a new record will send an SQL +INSERT+ operation to the database. Updating an existing record will send an SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not trigger the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an object in the database that's invalid. You can choose to have specific validations run when an object is created, saved, or updated. +Creating and saving a new record will send an SQL +INSERT+ operation to the database. Updating an existing record will send an SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated. CAUTION: There are many ways to change the state of an object in the database. Some methods will trigger validations, but some will not. This means that it's possible to save an object in the database in an invalid state if you aren't careful. -The following methods trigger validations, and will save the object to the database only if the object is valid. The bang versions (e.g. +save!+) will raise an exception if the record is invalid. The non-bang versions (e.g. +save+) simply return +false+. +The following methods trigger validations, and will save the object to the database only if the object is valid: * +create+ * +create!+ @@ -71,6 +71,8 @@ The following methods trigger validations, and will save the object to the datab * +update_attributes+ * +update_attributes!+ +The bang versions (e.g. +save!+) raise an exception if the record is invalid. The non-bang versions don't: +save+ and +update_attributes+ return +false+, +create+ and +update+ just return the object/s. + h4. Skipping Validations The following methods skip validations, and will save the object to the database regardless of its validity. They should be used with caution. @@ -84,11 +86,11 @@ The following methods skip validations, and will save the object to the database * +update_attribute+ * +update_counters+ -Note that +save+ also has the ability to skip validations (and callbacks!) if passed +false+. This technique should be used with caution. +Note that +save+ also has the ability to skip validations if passed +false+ as argument. This technique should be used with caution. * +save(false)+ -h4. Object#valid? and Object#invalid? +h4. +valid?+ and +invalid?+ To verify whether or not an object is valid, Rails uses the +valid?+ method. You can also use this method on your own. +valid?+ triggers your validations and returns true if no errors were added to the object, and false otherwise. @@ -101,7 +103,7 @@ Person.create(:name => "John Doe").valid? # => true Person.create(:name => nil).valid? # => false -When Active Record is performing validations, any errors found are collected into an +errors+ instance variable and can be accessed through an +errors+ instance method. An object is considered invalid if it has errors, and calling +save+ or +save!+ will not save it to the database. +When Active Record is performing validations, any errors found can be accessed through the +errors+ instance method. By definition an object is valid if this collection is empty after running validations. Note that an object instantiated with +new+ will not report errors even if it's technically invalid, because validations are not run when using +new+. @@ -135,7 +137,11 @@ end => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank -To verify whether or not a particular attribute of an object is valid, you can use the +invalid?+ method. This method is only useful _after_ validations have been run, because it only inspects the errors collection and does not trigger validations itself. It's different from the +valid?+ method because it doesn't verify the validity of the object as a whole, but only if there are errors found on an individual attribute of the object. ++invalid?+ is simply the inverse of +valid?+. +invalid?+ triggers your validations and returns true if any errors were added to the object, and false otherwise. + +h4. +errors.invalid?+ + +To verify whether or not a particular attribute of an object is valid, you can use the +errors.invalid?+ method. This method is only useful _after_ validations have been run, because it only inspects the errors collection and does not trigger validations itself. It's different from the +ActiveRecord::Base#invalid?+ method explained above because it doesn't verify the validity of the object as a whole. It only checks to see whether there are errors found on an individual attribute of the object. class Person < ActiveRecord::Base @@ -146,19 +152,19 @@ end >> Person.create.errors.invalid?(:name) # => true -We'll cover validation errors in greater depth in the *Working with Validation Errors* section. For now, let's turn to the built-in validation helpers that Rails provides by default. +We'll cover validation errors in greater depth in the "Working with Validation Errors":#working-with-validation-errors section. For now, let's turn to the built-in validation helpers that Rails provides by default. h3. Validation Helpers -Active Record offers many pre-defined validation helpers that you can use directly inside your class definitions. These helpers create validation rules that are commonly used. Every time a validation fails, an error message is added to the object's +errors+ collection, and this message is associated with the field being validated. +Active Record offers many pre-defined validation helpers that you can use directly inside your class definitions. These helpers provide common validation rules. Every time a validation fails, an error message is added to the object's +errors+ collection, and this message is associated with the field being validated. -Each helper accepts an arbitrary number of attributes identified by symbols, so with a single line of code you can add the same kind of validation to several attributes. +Each helper accepts an arbitrary number of attribute names, so with a single line of code you can add the same kind of validation to several attributes. -All these helpers accept the +:on+ and +:message+ options, which define when the validation should be applied and what message should be added to the +errors+ collection when it fails, respectively. The +:on+ option takes one of the values +:save+ (the default), +:create+ or +:update+. There is a default error message for each one of the validation helpers. These messages are used when the +:message+ option isn't used. Let's take a look at each one of the available helpers. +All of them accept the +:on+ and +:message+ options, which define when the validation should be run and what message should be added to the +errors+ collection if it fails, respectively. The +:on+ option takes one of the values +:save+ (the default), +:create+ or +:update+. There is a default error message for each one of the validation helpers. These messages are used when the +:message+ option isn't specified. Let's take a look at each one of the available helpers. -h4. validates_acceptance_of +h4. +validates_acceptance_of+ -Validates that a checkbox on the user interface was checked when a form was submitted. This is normally used when the user needs to agree to your application's terms of service, confirm reading some text, or any similar concept. This validation is very specific to web applications and actually this 'acceptance' does not need to be recorded anywhere in your database (if you don't have a field for it, the helper will just create a virtual attribute). +Validates that a checkbox on the user interface was checked when a form was submitted. This is typically used when the user needs to agree to your application's terms of service, confirm reading some text, or any similar concept. This validation is very specific to web applications and this 'acceptance' does not need to be recorded anywhere in your database (if you don't have a field for it, the helper will just create a virtual attribute). class Person < ActiveRecord::Base @@ -166,7 +172,7 @@ class Person < ActiveRecord::Base end -The default error message for +validates_acceptance_of+ is "_must be accepted_" +The default error message for +validates_acceptance_of+ is "_must be accepted_". +validates_acceptance_of+ can receive an +:accept+ option, which determines the value that will be considered acceptance. It defaults to "1", but you can change this. @@ -176,7 +182,7 @@ class Person < ActiveRecord::Base end -h4. validates_associated +h4. +validates_associated+ You should use this helper when your model has associations with other models and they also need to be validated. When you try to save your object, +valid?+ will be called upon each one of the associated objects. @@ -187,15 +193,15 @@ class Library < ActiveRecord::Base end -This validation will work with all the association types. +This validation will work with all of the association types. -CAUTION: Don't use +validates_associated+ on both ends of your associations, because this will lead to several recursive calls and blow up the method calls' stack. +CAUTION: Don't use +validates_associated+ on both ends of your associations, they would call each other in an infinite loop. The default error message for +validates_associated+ is "_is invalid_". Note that each associated object will contain its own +errors+ collection; errors do not bubble up to the calling model. -h4. validates_confirmation_of +h4. +validates_confirmation_of+ -You should use this helper when you have two text fields that should receive exactly the same content. For example, you may want to confirm an email address or a password. This validation creates a virtual attribute, using the name of the field that has to be confirmed with '_confirmation' appended. +You should use this helper when you have two text fields that should receive exactly the same content. For example, you may want to confirm an email address or a password. This validation creates a virtual attribute whose name is the name of the field that has to be confirmed with "_confirmation" appended. class Person < ActiveRecord::Base @@ -210,7 +216,7 @@ In your view template you could use something like <%= text_field :person, :email_confirmation %> -NOTE: This check is performed only if +email_confirmation+ is not nil, and by default only on save. To require confirmation, make sure to add a presence check for the confirmation attribute (we'll take a look at +validates_presence_of+ later on this guide): +This check is performed only if +email_confirmation+ is not +nil+. To require confirmation, make sure to add a presence check for the confirmation attribute (we'll take a look at +validates_presence_of+ later on this guide): class Person < ActiveRecord::Base @@ -219,54 +225,54 @@ class Person < ActiveRecord::Base end -The default error message for +validates_confirmation_of+ is "_doesn't match confirmation_" +The default error message for +validates_confirmation_of+ is "_doesn't match confirmation_". -h4. validates_exclusion_of +h4. +validates_exclusion_of+ This helper validates that the attributes' values are not included in a given set. In fact, this set can be any enumerable object. -class MovieFile < ActiveRecord::Base - validates_exclusion_of :format, :in => %w(mov avi), - :message => "Extension %s is not allowed" +class Account < ActiveRecord::Base + validates_exclusion_of :subdomain, :in => %w(www), + :message => "Subdomain {{value}} is reserved." end -The +validates_exclusion_of+ helper has an option +:in+ that receives the set of values that will not be accepted for the validated attributes. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. This example uses the +:message+ option to show how you can personalize it with the current attribute's value, through the +%s+ format mask. +The +validates_exclusion_of+ helper has an option +:in+ that receives the set of values that will not be accepted for the validated attributes. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. This example uses the +:message+ option to show how you can include the attribute's value. The default error message for +validates_exclusion_of+ is "_is not included in the list_". -h4. validates_format_of +h4. +validates_format_of+ -This helper validates the attributes' values by testing whether they match a given pattern. This pattern must be specified using a Ruby regular expression, which is specified using the +:with+ option. +This helper validates the attributes' values by testing whether they match a given regular expresion, which is specified using the +:with+ option. class Product < ActiveRecord::Base - validates_format_of :description, :with => /^[a-zA-Z]+$/, + validates_format_of :legacy_code, :with => /\A[a-zA-Z]+\z/, :message => "Only letters allowed" end The default error message for +validates_format_of+ is "_is invalid_". -h4. validates_inclusion_of +h4. +validates_inclusion_of+ This helper validates that the attributes' values are included in a given set. In fact, this set can be any enumerable object. class Coffee < ActiveRecord::Base validates_inclusion_of :size, :in => %w(small medium large), - :message => "%s is not a valid size" + :message => "{{value}} is not a valid size" end -The +validates_inclusion_of+ helper has an option +:in+ that receives the set of values that will be accepted. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. The previous example uses the +:message+ option to show how you can personalize it with the current attribute's value, through the +%s+ format mask. +The +validates_inclusion_of+ helper has an option +:in+ that receives the set of values that will be accepted. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. The previous example uses the +:message+ option to show how you can include the attribute's value. The default error message for +validates_inclusion_of+ is "_is not included in the list_". -h4. validates_length_of +h4. +validates_length_of+ -This helper validates the length of your attribute's value. It includes a variety of different options, so you can specify length constraints in different ways: +This helper validates the length of the attributes' values. It provides a variety of options, so you can specify length constraints in different ways: class Person < ActiveRecord::Base @@ -281,24 +287,46 @@ The possible length constraint options are: * +:minimum+ - The attribute cannot have less than the specified length. * +:maximum+ - The attribute cannot have more than the specified length. -* +:in+ (or +:within+) - The attribute length must be included in a given interval. The value for this option must be a Ruby range. -* +:is+ - The attribute length must be equal to a given value. +* +:in+ (or +:within+) - The attribute length must be included in a given interval. The value for this option must be a range. +* +:is+ - The attribute length must be equal to the given value. -The default error messages depend on the type of length validation being performed. You can personalize these messages, using the +:wrong_length+, +:too_long+ and +:too_short+ options and the +%d+ format mask as a placeholder for the number corresponding to the length constraint being used. You can still use the +:message+ option to specify an error message. +The default error messages depend on the type of length validation being performed. You can personalize these messages using the +:wrong_length+, +:too_long+, and +:too_short+ options and {{count}} as a placeholder for the number corresponding to the length constraint being used. You can still use the +:message+ option to specify an error message. class Person < ActiveRecord::Base - validates_length_of :bio, :too_long => "you're writing too much. %d characters is the maximum allowed." + validates_length_of :bio, :maximum => 1000, + :too_long => "{{count}} characters is the maximum allowed" +end + + +This helper counts characters by default, but you can split the value in a different way using the +:tokenizer+ option: + + +class Essay < ActiveRecord::Base + validates_length_of :content, + :minimum => 300, + :maximum => 400, + :tokenizer => lambda { |str| str.scan(/\w+/) }, + :too_short => "must have at least {{count}} words", + :too_long => "must have at most {{count}} words" end The +validates_size_of+ helper is an alias for +validates_length_of+. -h4. validates_numericality_of +h4. +validates_numericality_of+ -This helper validates that your attributes have only numeric values. By default, it will match an optional sign followed by a integral or floating point number. Using the +:integer_only+ option set to true, you can specify that only integral numbers are allowed. +This helper validates that your attributes have only numeric values. By default, it will match an optional sign followed by an integral or floating point number. To specify that only integral numbers are allowed set +:only_integer+ to true. -If you set +:integer_only+ to +true+, then it will use the +$$/\A[+\-]?\d+\Z/+ regular expression to validate the attribute's value. Otherwise, it will try to convert the value to a number using +Kernel.Float+. +If you set +:only_integer+ to +true+, then it will use the + + +/\A[+-]?\d+\Z/ + + +regular expression to validate the attribute's value. Otherwise, it will try to convert the value to a number using +Float+. + +WARNING. Note that the regular expression above allows a trailing newline character. class Player < ActiveRecord::Base @@ -309,19 +337,19 @@ end Besides +:only_integer+, the +validates_numericality_of+ helper also accepts the following options to add constraints to acceptable values: -* +:greater_than+ - Specifies the value must be greater than the supplied value. The default error message for this option is "_must be greater than (value)_" -* +:greater_than_or_equal_to+ - Specifies the value must be greater than or equal the supplied value. The default error message for this option is "_must be greater than or equal to (value)_" -* +:equal_to+ - Specifies the value must be equal to the supplied value. The default error message for this option is "_must be equal to (value)_" -* +:less_than+ - Specifies the value must be less than the supplied value. The default error message for this option is "_must e less than (value)_" -* +:less_than_or_equal_to+ - Specifies the value must be less than or equal the supplied value. The default error message for this option is "_must be less or equal to (value)_" -* +:odd+ - Specifies the value must be an odd number if set to true. The default error message for this option is "_must be odd_" -* +:even+ - Specifies the value must be an even number if set to true. The default error message for this option is "_must be even_" +* +:greater_than+ - Specifies the value must be greater than the supplied value. The default error message for this option is "_must be greater than {{count}}_". +* +:greater_than_or_equal_to+ - Specifies the value must be greater than or equal to the supplied value. The default error message for this option is "_must be greater than or equal to {{count}}". +* +:equal_to+ - Specifies the value must be equal to the supplied value. The default error message for this option is "_must be equal to {{count}}_". +* +:less_than+ - Specifies the value must be less than the supplied value. The default error message for this option is "_must be less than {{count}}_". +* +:less_than_or_equal_to+ - Specifies the value must be less than or equal the supplied value. The default error message for this option is "_must be less or equal to {{count}}_". +* +:odd+ - Specifies the value must be an odd number if set to true. The default error message for this option is "_must be odd_". +* +:even+ - Specifies the value must be an even number if set to true. The default error message for this option is "_must be even_". The default error message for +validates_numericality_of+ is "_is not a number_". -h4. validates_presence_of +h4. +validates_presence_of+ -This helper validates that the specified attributes are not empty. It uses the +blank?+ method to check if the value is either +nil+ or an empty string (if the string has only spaces, it will still be considered empty). +This helper validates that the specified attributes are not empty. It uses the +blank?+ method to check if the value is either +nil+ or a blank string, that is, a string that is either empty or consists of whitespace. class Person < ActiveRecord::Base @@ -338,13 +366,13 @@ class LineItem < ActiveRecord::Base end -If you want to validate the presence of a boolean field (where the real values are true and false), you should use +validates_inclusion_of :field_name, :in => [true, false]+. This is due to the way that +Object#blank?+ handles boolean values (+false.blank? # => true+). +Since +false.blank?+ is true, if you want to validate the presence of a boolean field you should use +validates_inclusion_of :field_name, :in => [true, false]+. The default error message for +validates_presence_of+ is "_can't be empty_". -h4. validates_uniqueness_of +h4. +validates_uniqueness_of+ -This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint directly into your database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create an unique index in your database. +This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create an unique index in your database. class Account < ActiveRecord::Base @@ -352,14 +380,14 @@ class Account < ActiveRecord::Base end -The validation happens by performing a SQL query into the model's table, searching for a record where the attribute that must be validated is equal to the value in the object being validated. +The validation happens by performing a SQL query into the model's table, searching for an existing record with the same value in that attribute. There is a +:scope+ option that you can use to specify other attributes that are used to limit the uniqueness check: class Holiday < ActiveRecord::Base validates_uniqueness_of :name, :scope => :year, - :message => "Should happen once per year" + :message => "should happen once per year" end @@ -371,16 +399,18 @@ class Person < ActiveRecord::Base end +WARNING. Note that some databases are configured to perform case-insensitive searches anyway. + The default error message for +validates_uniqueness_of+ is "_has already been taken_". -h4. validates_each +h4. +validates_each+ This helper validates attributes against a block. It doesn't have a predefined validation function. You should create one using a block, and every attribute passed to +validates_each+ will be tested against it. In the following example, we don't want names and surnames to begin with lower case. class Person < ActiveRecord::Base validates_each :name, :surname do |model, attr, value| - model.errors.add(attr, 'Must start with upper case') if value =~ /^[a-z]/ + model.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/ end end @@ -389,22 +419,22 @@ The block receives the model, the attribute's name and the attribute's value. Yo h3. Common Validation Options -There are some common options that all the validation helpers can use. Here they are, except for the +:if+ and +:unless+ options, which are discussed later in the conditional validation topic. +There are some common options that all the validation helpers can use. Here they are, except for the +:if+ and +:unless+ options, which are discussed later in "Conditional Validation":#conditional-validation. -h4. :allow_nil +h4. +:allow_nil+ -The +:allow_nil+ option skips the validation when the value being validated is +nil+. You may be asking yourself if it makes any sense to use +:allow_nil+ and +validates_presence_of+ together. Well, it does. Remember, the validation will be skipped only for +nil+ attributes, but empty strings are not considered +nil+. +The +:allow_nil+ option skips the validation when the value being validated is +nil+. Using +:allow_nil+ with +validates_presence_of+ allows for +nil+, but any other +blank?+ value will still be rejected. class Coffee < ActiveRecord::Base validates_inclusion_of :size, :in => %w(small medium large), - :message => "%s is not a valid size", :allow_nil => true + :message => "{{value}} is not a valid size", :allow_nil => true end -h4. :allow_blank +h4. +:allow_blank+ -The +:allow_blank+ option is similar to the +:allow_nil+ option. This option will let validation pass if the attribute's value is +nil+ or an empty string, i.e., any value that returns +true+ for +blank?+. +The +:allow_blank+ option is similar to the +:allow_nil+ option. This option will let validation pass if the attribute's value is +blank?+, like +nil+ or an empty string for example. class Topic < ActiveRecord::Base @@ -415,32 +445,32 @@ Topic.create("title" => "").valid? # => true Topic.create("title" => nil).valid? # => true -h4. :message +h4. +:message+ -As you've already seen, the +:message+ option lets you specify the message that will be added to the +errors+ collection when validation fails. When this option is not used, Active Record will use the respective default error message for each validation helper, together with the attribute name. +As you've already seen, the +:message+ option lets you specify the message that will be added to the +errors+ collection when validation fails. When this option is not used, Active Record will use the respective default error message for each validation helper. -h4. :on +h4. +:on+ The +:on+ option lets you specify when the validation should happen. The default behavior for all the built-in validation helpers is to be ran on save (both when you're creating a new record and when you're updating it). If you want to change it, you can use +:on => :create+ to run the validation only when a new record is created or +:on => :update+ to run the validation only when a record is updated. class Person < ActiveRecord::Base - # => it will be possible to update email with a duplicated value + # it will be possible to update email with a duplicated value validates_uniqueness_of :email, :on => :create - # => it will be possible to create the record with a 'non-numerical age' + # it will be possible to create the record with a non-numerical age validates_numericality_of :age, :on => :update - # => the default (validates on both create and update) + # the default (validates on both create and update) validates_presence_of :name, :on => :save end 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 Ruby 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 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. -h4. Using a Symbol with :if and :unless +h4. Using a Symbol with +:if+ and +:unless+ You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option. @@ -454,9 +484,9 @@ class Order < ActiveRecord::Base end -h4. Using a String with :if and :unless +h4. Using a String with +:if+ and +:unless+ -You can also use a string that will be evaluated using +:eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition. +You can also use a string that will be evaluated using +eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition. class Person < ActiveRecord::Base @@ -464,9 +494,9 @@ class Person < ActiveRecord::Base end -h4. Using a Proc with :if and :unless +h4. Using a Proc with +:if+ and +:unless+ -Finally, it's possible to associate +:if+ and +:unless+ with a Ruby Proc object which will be called. Using a Proc object can give you the ability to write a condition that will be executed only when the validation happens and not when your code is loaded by the Ruby interpreter. This option is best suited when writing short validation methods, usually one-liners. +Finally, it's possible to associate +:if+ and +:unless+ with a +Proc+ object which will be called. Using a +Proc+ object gives you the ability to write an inline condition instead of a separate method. This option is best suited for one-liners. class Account < ActiveRecord::Base @@ -481,12 +511,12 @@ When the built-in validation helpers are not enough for your needs, you can writ Simply 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 pass more than one symbol for each class method and the respective validations will be ran in the same order as they were registered. +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. class Invoice < ActiveRecord::Base validate :expiration_date_cannot_be_in_the_past, - :discount_cannot_be_more_than_total_value + :discount_cannot_be_greater_than_total_value def expiration_date_cannot_be_in_the_past errors.add(:expiration_date, "can't be in the past") if @@ -494,8 +524,8 @@ class Invoice < ActiveRecord::Base end def discount_cannot_be_greater_than_total_value - errors.add(:discount, "can't be greater than total value") unless - discount <= total_value + errors.add(:discount, "can't be greater than total value") if + discount > total_value end end @@ -503,25 +533,18 @@ end You can even create your own validation helpers and reuse them in several different models. Here is an example where we create a custom validation helper to validate the format of fields that represent email addresses: -module ActiveRecord - module Validations - module ClassMethods - def validates_email_format_of(value) - validates_format_of value, - :with => /\A[\w\._%-]+@[\w\.-]+\.[a-zA-Z]{2,4}\z/, - :if => Proc.new { |u| !u.email.blank? }, - :message => "Invalid format for email address" - end - end +ActiveRecord::Base.class_eval do + def self.validates_as_radio(attr_name, n, options={}) + validates_inclusion_of attr_name, {:in => 1..n}.merge(options) end end -Simply create a new validation method inside the +ActiveRecord::Validations::ClassMethods+ module. You can put this code in a file inside your application's *lib* folder, and then requiring it from your *environment.rb* or any other file inside *config/initializers*. You can use this helper like this: +Simply reopen +ActiveRecord::Base+ and define a class method like that. You'd typically put this code somewhere in +config/initializers+. You can use this helper like this: -class Person < ActiveRecord::Base - validates_email_format_of :email_address +class Movie < ActiveRecord::Base + validates_as_radio :rating, 5 end @@ -529,11 +552,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 an exhaustive list that covers all of the available methods. +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. -h4. errors.add_to_base +h4. +errors.add_to_base+ -+add_to_base+ lets you add errors messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of it's attributes. +add_to_base+ simply receives a string and uses this as the error message. +The +add_to_base+ method lets you add errors messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. +add_to_base+ simply receives a string and uses this as the error message. class Person < ActiveRecord::Base @@ -543,29 +566,29 @@ class Person < ActiveRecord::Base end -h4. errors.add +h4. +errors.add+ -+add+ lets you manually add messages that are related to particular attributes. Note that Rails will prepend the name of the attribute to the error message you pass it. You can use the +full_messages+ method to view the messages in the form they might be displayed to a user. +add+ receives a symbol with the name of the attribute that you want to add the message to, and the message itself. +The +add+ method lets you manually add messages that are related to particular attributes. You can use the +full_messages+ method to view the messages in the form they might be displayed to a user. Those particular messages get the attribute name prepended (and capitalized). +add+ receives the name of the attribute you want to add the message to, and the message itself. class Person < ActiveRecord::Base def a_method_used_for_validation_purposes - errors.add(:name, "cannot contain the characters !@#$%*()_-+=") + errors.add(:name, "cannot contain the characters !@#%*()_-+=") end end -person = Person.create(:name => "!@#$") +person = Person.create(:name => "!@#") person.errors.on(:name) -# => "is too short (minimum is 3 characters)" + # => "cannot contain the characters !@#%*()_-+=" person.errors.full_messages -# => ["Name is too short (minimum is 3 characters)"] + # => ["Name cannot contain the characters !@#%*()_-+="] -h4. errors.on +h4. +errors.on+ -+on+ is used when you want to check the error messages for a specific attribute. It will return different kinds of objects depending on the state of the +errors+ collection for the given attribute. If there are no errors related to the attribute, +on+ will return +nil+. If there is just one errors message for this attribute, +on+ will return a string with the message. When +errors+ holds two or more error messages for the attribute, +on+ will return an array of strings, each one with one error message. +The +on+ method is used when you want to check the error messages for a specific attribute. It returns different kinds of objects depending on the state of the +errors+ collection for the given attribute. If there are no errors related to the attribute +on+ returns +nil+. If there is just one error message for this attribute +on+ returns a string with the message. When +errors+ holds two or more error messages for the attribute, +on+ returns an array of strings, each one with one error message. class Person < ActiveRecord::Base @@ -580,17 +603,17 @@ person.errors.on(:name) # => nil person = Person.new(:name => "JD") person.valid? # => false person.errors.on(:name) -# => "is too short (minimum is 3 characters)" + # => "is too short (minimum is 3 characters)" person = Person.new person.valid? # => false person.errors.on(:name) -# => ["can't be blank", "is too short (minimum is 3 characters)"] + # => ["can't be blank", "is too short (minimum is 3 characters)"] -h4. errors.clear +h4. +errors.clear+ -+clear+ is used when you intentionally want to clear all the messages in the +errors+ collection. Of course, calling +errors.clear+ upon an invalid object won't actually make it valid: the +errors+ collection will now be empty, but the next time you call +valid?+ or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the +errors+ collection will be filled again. +The +clear+ method is used when you intentionally want to clear all the messages in the +errors+ collection. Of course, calling +errors.clear+ upon an invalid object won't actually make it valid: the +errors+ collection will now be empty, but the next time you call +valid?+ or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the +errors+ collection will be filled again. class Person < ActiveRecord::Base @@ -601,7 +624,7 @@ end person = Person.new person.valid? # => false person.errors.on(:name) -# => ["can't be blank", "is too short (minimum is 3 characters)"] + # => ["can't be blank", "is too short (minimum is 3 characters)"] person.errors.clear person.errors.empty? # => true @@ -609,31 +632,36 @@ person.errors.empty? # => true p.save # => false p.errors.on(:name) -# => ["can't be blank", "is too short (minimum is 3 characters)"] + # => ["can't be blank", "is too short (minimum is 3 characters)"] -h4. errors.size +h4. +errors.size+ -+size+ returns the total number of errors added. Two errors added to the same object will be counted as such. +The +size+ method returns the total number of error messages for the object. class Person < ActiveRecord::Base validates_presence_of :name - validates_length_of :name, :minimum => 3 + validates_length_of :name, :minimum => 3 + validates_presence_of :email end person = Person.new person.valid? # => false -person.errors.size # => 2 +person.errors.size # => 3 + +person = Person.new(:name => "Andrea", :email => "andrea@example.com") +person.valid? # => true +person.errors.size # => 0 h3. Displaying Validation Errors in the View Rails provides built-in helpers to display the error messages of your models in your view templates. -h4. error_messages and error_messages_for +h4. +error_messages+ and +error_messages_for+ -When creating a form with the form_for helper, you can use the error_messages method on the form builder to render all failed validation messages for the current model instance. +When creating a form with the +form_for+ helper, you can use the +error_messages+ method on the form builder to render all failed validation messages for the current model instance. class Product < ActiveRecord::Base @@ -659,6 +687,8 @@ end <% end %> +To get the idea, if you submit the form with empty fields you typically get this back, though styles are indeed missing by default: + !images/error_messages.png(Error messages)! You can also use the +error_messages_for+ helper to display the error messages of a model assigned to a view template. It's very similar to the previous example and will achieve exactly the same result. @@ -685,53 +715,52 @@ If you pass +nil+ to any of these options, it will get rid of the respective sec h4. Customizing the Error Messages CSS -It's also possible to change the CSS classes used by the +error_messages+ helper. These classes are automatically defined at the *scaffold.css* file, generated by the scaffold script. If you're not using scaffolding, you can still define those CSS classes at your CSS files. Here is a list of the default CSS classes. +The selectors to customize the style of error messages are: -* +.fieldWithErrors+ - Style for the form fields with errors. +* +.fieldWithErrors+ - Style for the form fields and labels with errors. * +#errorExplanation+ - Style for the +div+ element with the error messages. * +#errorExplanation h2+ - Style for the header of the +div+ element. * +#errorExplanation p+ - Style for the paragraph that holds the message that appears right below the header of the +div+ element. -* +#errorExplanation ul li+ - Style for the list of error messages. +* +#errorExplanation ul li+ - Style for the list items with individual error messages. + +Scaffolding for example generates +public/stylesheets/scaffold.css+, which defines the red-based style you saw above. + +The name of the class and the id can be changed with the +:class+ and +:id+ options, accepted by both helpers. h4. Customizing the Error Messages HTML -By default, form fields with errors are displayed enclosed by a +div+ element with the +fieldWithErrors+ CSS class. However, it's possible to override the way Rails treats those fields by default. +By default, form fields with errors are displayed enclosed by a +div+ element with the +fieldWithErrors+ CSS class. However, it's possible to override that. + +The way form fields with errors are treated is defined by +ActionView::Base.field_error_proc+. This is a +Proc+ that receives two parameters: + +* A string with the HTML tag +* An instance of +ActionView::Helpers::InstanceTag+. Here is a simple example where we change the Rails behaviour to always display the error messages in front of each of the form fields with errors. The error messages will be enclosed by a +span+ element with a +validation-error+ CSS class. There will be no +div+ element enclosing the +input+ element, so we get rid of that red border around the text field. You can use the +validation-error+ CSS class to style it anyway you want. ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| if instance.error_message.kind_of?(Array) - %(#{html_tag}  + %(#{html_tag}  #{instance.error_message.join(',')}) else - %(#{html_tag}  + %(#{html_tag}  #{instance.error_message}) end end -This will result in something like the following content: +This will result in something like the following: !images/validation_error_messages.png(Validation error messages)! -The way form fields with errors are treated is defined by the +ActionView::Base.field_error_proc+ Ruby Proc. This Proc receives two parameters: - -* A string with the HTML tag -* An object of the +ActionView::Helpers::InstanceTag+ class. - h3. Callbacks Overview -Callbacks are methods that get called at certain moments of an object's lifecycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted or loaded from the database. - -# TODO discuss what does/doesn't trigger callbacks, like we did in the validations section (e.g. destroy versus delete). -# Consider moving up to the (new) intro overview section, before getting into details. -# http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002220 -# http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html +Callbacks are methods that get called at certain moments of an object's lifecycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database. h4. Callback Registration -In order to use the available callbacks, you need to register them. You can do that by implementing them as an ordinary methods, and then using a macro-style class method to register then as callbacks. +In order to use the available callbacks, you need to register them. You can do that by implementing them as ordinary methods, and then using a macro-style class method to register them as callbacks. class User < ActiveRecord::Base @@ -741,7 +770,7 @@ class User < ActiveRecord::Base protected def ensure_login_has_a_value - if self.login.nil? + if login.nil? self.login = email unless email.blank? end end @@ -754,19 +783,166 @@ The macro-style class methods can also receive a block. Consider using this styl class User < ActiveRecord::Base validates_presence_of :login, :email - before_create {|user| user.name = user.login.capitalize if user.name.blank?} + before_create {|user| user.name = user.login.capitalize + if user.name.blank?} end It's considered good practice to declare callback methods as being protected or private. If left public, they can be called from outside of the model and violate the principle of object encapsulation. +h3. Available Callbacks + +Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations: + +h4. Creating an Object + +* +before_validation+ +* +before_validation_on_create+ +* +after_validation+ +* +after_validation_on_create+ +* +before_save+ +* +before_create+ +* INSERT OPERATION +* +after_create+ +* +after_save+ + +h4. Updating an Object + +* +before_validation+ +* +before_validation_on_update+ +* +after_validation+ +* +after_validation_on_update+ +* +before_save+ +* +before_update+ +* UPDATE OPERATION +* +after_update+ +* +after_save+ + +h4. Destroying an Object + +* +before_destroy+ +* DELETE OPERATION +* +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. + +h4. +after_initialize+ and +after_find+ + +The +after_initialize+ callback will be called whenever an Active Record object is instantiated, either by directly using +new+ or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record +initialize+ method. + +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 the only way to register them is by defining them as regular methods. If you try to register +after_initialize+ or +after_find+ using macro-style class methods, they will just be ignored. This behaviour is due to performance reasons, since +after_initialize+ and +after_find+ will both be called for each record found in the database, significantly slowing down the queries. + + +class User < ActiveRecord::Base + def after_initialize + puts "You have initialized an object!" + end + + def after_find + puts "You have found an object!" + end +end + +>> User.new +You have initialized an object! +=> # + +>> User.first +You have found an object! +You have initialized an object! +=> # + + +h3. Running Callbacks + +The following methods trigger callbacks: + +* +create+ +* +create!+ +* +decrement!+ +* +destroy+ +* +destroy_all+ +* +increment!+ +* +save+ +* +save!+ +* +save(false)+ +* +toggle!+ +* +update+ +* +update_attribute+ +* +update_attributes+ +* +update_attributes!+ +* +valid?+ + +Additionally, the +after_find+ callback is triggered by the following finder methods: + +* +all+ +* +first+ +* +find+ +* +find_all_by_attribute+ +* +find_by_attribute+ +* +find_by_attribute!+ +* +last+ + +The +after_initialize+ callback is triggered every time a new object of the class is initialized. + +h3. Skipping Callbacks + +Just as with validations, it's also possible to skip callbacks. These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential implications may lead to invalid data. + +* +decrement+ +* +decrement_counter+ +* +delete+ +* +delete_all+ +* +find_by_sql+ +* +increment+ +* +increment_counter+ +* +toggle+ +* +update_all+ +* +update_counters+ + +h3. Halting Execution + +As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed. + +The whole callback chain is wrapped in a transaction. If any before callback method returns exactly +false+ or raises an exception the execution chain gets halted and a ROLLBACK is issued. After callbacks can only accomplish that by raising an exception. + +WARNING. Raising an arbitrary exception may break code that expects +save+ and friends not to fail like that. The +ActiveRecord::Rollback+ exception is thought precisely to tell Active Record a rollback is going on. That one is internally captured but not reraised. + +h3. Relational Callbacks + +Callbacks work through model relationships, and can even be defined by them. Let's take an example where a user has many posts. In our example, a user's posts should be destroyed if the user is destroyed. So, we'll add an +after_destroy+ callback to the +User+ model by way of its relationship to the +Post+ model. + + +class User < ActiveRecord::Base + has_many :posts, :dependent => :destroy +end + +class Post < ActiveRecord::Base + after_destroy :log_destroy_action + + def log_destroy_action + puts 'Post destroyed' + end +end + +>> user = User.first +=> # +>> user.posts.create! +=> # +>> user.destroy +Post destroyed +=> # + + h3. Conditional Callbacks -Like in validations, we can also make our callbacks conditional, calling then only 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 Ruby Proc. You may use the +:if+ option when you want to specify when the callback *should* get called. If you want to specify when the callback *should not* be called, then you may use the +:unless+ option. +Like in validations, we can also make our callbacks conditional, calling them only 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 callback *should* get called. If you want to specify when the callback *should not* be called, then you may use the +:unless+ option. -h4. Using :if and :unless with a Symbol +h4. Using +:if+ and +:unless+ with a Symbol -You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns +false+ the callback won't be executed. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check the if the callback should be executed. +You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns +false+ the callback won't be executed. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed. class Order < ActiveRecord::Base @@ -774,9 +950,9 @@ class Order < ActiveRecord::Base end -h4. Using :if and :unless with a String +h4. Using +:if+ and +:unless+ with a String -You can also use a string that will be evaluated using +:eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition. +You can also use a string that will be evaluated using +eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition. class Order < ActiveRecord::Base @@ -784,9 +960,9 @@ class Order < ActiveRecord::Base end -h4. Using :if and :unless with a Proc +h4. Using +:if+ and +:unless+ with a Proc -Finally, it's possible to associate +:if+ and +:unless+ with a Ruby Proc object. This option is best suited when writing short validation methods, usually one-liners. +Finally, it's possible to associate +:if+ and +:unless+ with a +Proc+ object. This option is best suited when writing short validation methods, usually one-liners. class Order < ActiveRecord::Base @@ -806,64 +982,17 @@ class Comment < ActiveRecord::Base end -h3. Available Callbacks - -Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations. - -h4. Creating and/or Updating an Object - -* +before_validation+ -* +after_validation+ -* +before_save+ -* INSERT OR UPDATE OPERATION -* +after_save+ - -h4. Creating an Object - -* +before_validation_on_create+ -* +after_validation_on_create+ -* +before_create+ -* INSERT OPERATION -* +after_create+ - -h4. Updating an Object - -* +before_validation_on_update+ -* +after_validation_on_update+ -* +before_update+ -* UPDATE OPERATION -* +after_update+ - -h4. Destroying an Object - -* +before_destroy+ -* DELETE OPERATION -* +after_destroy+ - -CAUTION: The +before_destroy+ and +after_destroy+ callbacks will only be called if you delete the model using either the +destroy+ instance method or one of the +destroy+ or +destroy_all+ class methods of your Active Record class. If you use +delete+ or +delete_all+ no callback operations will run, since Active Record will not instantiate any objects, accessing the records to be deleted directly in the database. - -h4. after_initialize and after_find - -The +after_initialize+ callback will be called whenever an Active Record object is instantiated, either by directly using +new+ or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record +initialize+ method. - -The +after_find+ callback will be called whenever Active Record loads a record from the database. When used together with +after_initialize+ it will run first, since Active Record will first read the record from the database and them create the model object that will hold it. - -The +after_initialize+ and +after_find+ callbacks are a bit different from the others, since the only way to register those callbacks is by defining them as methods. If you try to register +after_initialize+ or +after_find+ using macro-style class methods, they will just be ignored. This behaviour is due to performance reasons, since +after_initialize+ and +after_find+ will both be called for each record found in the database, significantly slowing down the queries. - -h3. Halting Execution - -As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks and the database operation to be executed. However, if at any moment one of the +before_create+, +before_save+, +before_update+ or +before_destroy+ callback methods returns a boolean +false+ (not +nil+) value or raise and exception, this execution chain will be halted and the desired operation will not complete: your model will not get persisted in the database, or your records will not get deleted and so on. It's because the whole callback chain is wrapped in a transaction, so raising an exception or returning +false+ fires a database ROLLBACK. - h3. Callback Classes -Sometimes the callback methods that you'll write will be useful enough to be reused at other models. Active Record makes it possible to create classes that encapsulate the callback methods, so it becomes very easy to reuse them. +Sometimes the callback methods that you'll write will be useful enough to be reused by other models. Active Record makes it possible to create classes that encapsulate the callback methods, so it becomes very easy to reuse them. -Here's an example where we create a class with a after_destroy callback for a PictureFile model. +Here's an example where we create a class with an +after_destroy+ callback for a +PictureFile+ model. class PictureFileCallbacks def after_destroy(picture_file) - File.delete(picture_file.filepath) if File.exists?(picture_file.filepath) + File.delete(picture_file.filepath) + if File.exists?(picture_file.filepath) end end @@ -876,17 +1005,18 @@ class PictureFile < ActiveRecord::Base end -Note that we needed to instantiate a new PictureFileCallbacks object, since we declared our callback as an instance method. Sometimes it will make more sense to have it as a class method. +Note that we needed to instantiate a new +PictureFileCallbacks+ object, since we declared our callback as an instance method. Sometimes it will make more sense to have it as a class method. class PictureFileCallbacks def self.after_destroy(picture_file) - File.delete(picture_file.filepath) if File.exists?(picture_file.filepath) + File.delete(picture_file.filepath) + if File.exists?(picture_file.filepath) end end -If the callback method is declared this way, it won't be necessary to instantiate a PictureFileCallbacks object. +If the callback method is declared this way, it won't be necessary to instantiate a +PictureFileCallbacks+ object. class PictureFile < ActiveRecord::Base @@ -898,9 +1028,9 @@ You can declare as many callbacks as you want inside your callback classes. h3. Observers -Observers are similar to callbacks, but with important differences. Whereas callbacks can pollute a model with code that isn't directly related to its purpose, observers allow you to add functionality outside of a model. For example, it could be argued that a +User+ model should not include code to send registration confirmation emails. Whenever you use callbacks with code that isn't directly related to your model, you may want to consider creating an observer instead. +Observers are similar to callbacks, but with important differences. Whereas callbacks can pollute a model with code that isn't directly related to its purpose, observers allow you to add the same functionality outside of a model. For example, it could be argued that a +User+ model should not include code to send registration confirmation emails. Whenever you use callbacks with code that isn't directly related to your model, you may want to consider creating an observer instead. -h4. Creating observers +h4. Creating Observers For example, imagine a +User+ model where we want to send an email every time a new user is created. Because sending emails is not directly related to our model's purpose, we could create an observer to contain this functionality. @@ -916,18 +1046,18 @@ As with callback classes, the observer's methods receive the observed model as a h4. Registering Observers -Observers should be placed inside of your *app/models* directory and registered in your application's *config/environment.rb* file. For example, the +UserObserver+ above would be saved as *app/models/user_observer.rb* and registered in *config/environment.rb*. +Observers are conventionally placed inside of your +app/models+ directory and registered in your application's +config/environment.rb+ file. For example, the +UserObserver+ above would be saved as +app/models/user_observer.rb+ and registered in +config/environment.rb+ this way: # Activate observers that should always be running config.active_record.observers = :user_observer -As usual, settings in *config/environments/* take precedence over those in *config/environment.rb*. So, if you prefer that an observer not run in all environments, you can simply register it in a specific environment instead. +As usual, settings in +config/environments+ take precedence over those in +config/environment.rb+. So, if you prefer that an observer not run in all environments, you can simply register it in a specific environment instead. h4. Sharing Observers -By default, Rails will simply strip 'observer' from an observer's name to find the model it should observe. However, observers can also be used to add behaviour to more than one model, and so it's possible to manually specify the models that our observer should observe. +By default, Rails will simply strip "Observer" from an observer's name to find the model it should observe. However, observers can also be used to add behaviour to more than one model, and so it's possible to manually specify the models that our observer should observe. class MailerObserver < ActiveRecord::Observer @@ -939,7 +1069,7 @@ class MailerObserver < ActiveRecord::Observer end -In this example, the +after_create+ method would be called whenever a +Registration+ or +User+ was created. Note that this new +MailerObserver+ would also need to be registered in *config/environment.rb* in order to take effect. +In this example, the +after_create+ method would be called whenever a +Registration+ or +User+ was created. Note that this new +MailerObserver+ would also need to be registered in +config/environment.rb+ in order to take effect. # Activate observers that should always be running @@ -950,6 +1080,7 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks +* March 7, 2009: Callbacks revision by Trevor Turk * February 10, 2009: Observers revision by Trevor Turk * February 5, 2009: Initial revision by Trevor Turk * January 9, 2009: Initial version by "Cássio Marques":credits.html#cmarques diff --git a/vendor/rails/railties/guides/source/association_basics.textile b/vendor/rails/railties/guides/source/association_basics.textile index 3c03c825..03e22bd6 100644 --- a/vendor/rails/railties/guides/source/association_basics.textile +++ b/vendor/rails/railties/guides/source/association_basics.textile @@ -76,7 +76,7 @@ In Rails, an _association_ is a connection between two Active Record models. Ass In the remainder of this guide, you'll learn how to declare and use the various forms of associations. But first, a quick introduction to the situations where each association type is appropriate. -h4. The +belongs_to+ association +h4. The +belongs_to+ Association A +belongs_to+ association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes customers and orders, and each order can be assigned to exactly one customer, you'd declare the order model this way: @@ -88,7 +88,7 @@ end !images/belongs_to.png(belongs_to Association Diagram)! -h4. The +has_one+ association +h4. The +has_one+ Association A +has_one+ association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model. For example, if each supplier in your application has only one account, you'd declare the supplier model like this: @@ -100,7 +100,7 @@ end !images/has_one.png(has_one Association Diagram)! -h4. The +has_many+ association +h4. The +has_many+ Association A +has_many+ association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a +belongs_to+ association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this: @@ -114,7 +114,7 @@ NOTE: The name of the other model is pluralized when declaring a +has_many+ asso !images/has_many.png(has_many Association Diagram)! -h4. The +has_many :through+ association +h4. The +has_many :through+ Association A +has_many :through+ association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding _through_ a third model. For example, consider a medical practice where patients make appointments to see physicians. The relevant association declarations could look like this: @@ -155,7 +155,7 @@ class Paragraph < ActiveRecord::Base end -h4. The +has_one :through+ association +h4. The +has_one :through+ Association A +has_one :through+ association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding _through_ a third model. For example, if each supplier has one account, and each account is associated with one account history, then the customer model could look like this: @@ -177,7 +177,7 @@ end !images/has_one_through.png(has_one :through Association Diagram)! -h4. The +has_and_belongs_to_many+ association +h4. The +has_and_belongs_to_many+ Association A +has_and_belongs_to_many+ association creates a direct many-to-many connection with another model, with no intervening model. For example, if your application includes assemblies and parts, with each assembly having many parts and each part appearing in many assemblies, you could declare the models this way: @@ -193,7 +193,7 @@ end !images/habtm.png(has_and_belongs_to_many Association Diagram)! -h4. Choosing between +belongs_to+ and +has_one+ +h4. Choosing Between +belongs_to+ and +has_one+ If you want to set up a 1–1 relationship between two models, you'll need to add +belongs_to+ to one, and +has_one+ to the other. How do you know which is which? @@ -235,7 +235,7 @@ end NOTE: Using +t.integer :supplier_id+ makes the foreign key naming obvious and explicit. In current versions of Rails, you can abstract away this implementation detail by using +t.references :supplier+ instead. -h4. Choosing between +has_many :through+ and +has_and_belongs_to_many+ +h4. Choosing Between +has_many :through+ and +has_and_belongs_to_many+ Rails offers two different ways to declare a many-to-many relationship between models. The simpler way is to use +has_and_belongs_to_many+, which allows you to make the association directly: @@ -272,7 +272,7 @@ The simplest rule of thumb is that you should set up a +has_many :through+ relat You should use +has_many :through+ if you need validations, callbacks, or extra attributes on the join model. -h4. Polymorphic associations +h4. Polymorphic Associations A slightly more advanced twist on associations is the _polymorphic association_. With polymorphic associations, a model can belong to more than one other model, on a single association. For example, you might have a picture model that belongs to either an employee model or a product model. Here's how this could be declared: @@ -333,7 +333,7 @@ end !images/polymorphic.png(Polymorphic Association Diagram)! -h4. Self joins +h4. Self Joins In designing a data model, you will sometimes find a model that should have a relation to itself. For example, you may want to store all employees in a single database model, but be able to trace relationships such as between manager and subordinates. This situation can be modeled with self-joining associations: @@ -356,7 +356,7 @@ Here are a few things you should know to make efficient use of Active Record ass * Updating the schema * Controlling association scope -h4. Controlling caching +h4. Controlling Caching All of the association methods are built around caching, which keeps the result of the most recent query available for further operations. The cache is even shared across methods. For example: @@ -375,15 +375,15 @@ customer.orders(true).empty? # discards the cached copy of orders # and goes back to the database -h4. Avoiding name collisions +h4. Avoiding Name Collisions You are not free to use just any name for your associations. Because creating an association adds a method with that name to the model, it is a bad idea to give an association a name that is already used for an instance method of +ActiveRecord::Base+. The association method would override the base method and break things. For instance, +attributes+ or +connection+ are bad names for associations. -h4. Updating the schema +h4. Updating the Schema Associations are extremely useful, but they are not magic. You are responsible for maintaining your database schema to match your associations. In practice, this means two things, depending on what sort of associations you are creating. For +belongs_to+ associations you need to create foreign keys, and for +has_and_belongs_to_many+ associations you need to create the appropriate join table. -h5. Creating foreign Keys for +belongs_to+ associations +h5. Creating Foreign Keys for +belongs_to+ Associations When you declare a +belongs_to+ association, you need to create foreign keys as appropriate. For example, consider this model: @@ -413,7 +413,7 @@ end If you create an association some time after you build the underlying model, you need to remember to create an +add_column+ migration to provide the necessary foreign key. -h5. Creating join tables for +has_and_belongs_to_many+ associations +h5. Creating Join Tables for +has_and_belongs_to_many+ Associations If you create a +has_and_belongs_to_many+ association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the +:join_table+ option, Active Record creates the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering. @@ -450,7 +450,7 @@ end We pass +:id => false+ to +create_table+ because that table does not represent a model. That's required for the association to work properly. If you observe any strange behaviour in a +has_and_belongs_to_many+ association like mangled models IDs, or exceptions about conflicting IDs chances are you forgot that bit. -h4. Controlling association scope +h4. Controlling Association Scope By default, associations look for objects only within the current module's scope. This can be important when you declare Active Record models within a module. For example: @@ -510,11 +510,11 @@ 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. -h4. +belongs_to+ association reference +h4. +belongs_to+ Association Reference The +belongs_to+ association creates a one-to-one match with another model. In database terms, this association says that this class contains the foreign key. If the other class contains the foreign key, then you should use +has_one+ instead. -h5. Methods added by +belongs_to+ +h5. Methods Added by +belongs_to+ When you declare a +belongs_to+ association, the declaring class automatically gains four methods related to the association: @@ -724,7 +724,7 @@ NOTE: There's no need to use +:include+ for immediate associations - that is, if h6. +:polymorphic+ -Passing +true+ to the +:polymorphic+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail
earlier in this guide. +Passing +true+ to the +:polymorphic+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail earlier in this guide. h6. +:readonly+ @@ -740,7 +740,7 @@ h6. +:validate+ If you set the +:validate+ option to +true+, then associated objects will be validated whenever you save this object. By default, this is +false+: associated objects will not be validated when this object is saved. -h5. How to know whether there's an associated object? +h5. How To Know Whether There's an Associated Object? To know whether there's and associated object just check association.nil?: @@ -750,15 +750,15 @@ if @order.customer.nil? end -h5. When are objects saved? +h5. When are Objects Saved? Assigning an object to a +belongs_to+ association does _not_ automatically save the object. It does not save the associated object either. -h4. +has_one+ association reference +h4. +has_one+ Association Reference The +has_one+ association creates a one-to-one match with another model. In database terms, this association says that the other class contains the foreign key. If this class contains the foreign key, then you should use +belongs_to+ instead. -h5. Methods added by +has_one+ +h5. Methods Added by +has_one+ When you declare a +has_one+ association, the declaring class automatically gains four methods related to the association: @@ -848,7 +848,7 @@ The +has_one+ association supports these options: h6. +:as+ -Setting the +:as+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail earlier in this guide. +Setting the +:as+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail earlier in this guide. h6. +:autosave+ @@ -952,13 +952,13 @@ The +:source_type+ option specifies the source association type for a +has_one : h6. :through -The +:through+ option specifies a join model through which to perform the query. +has_one :through+ associations were discussed in detail earlier in this guide. +The +:through+ option specifies a join model through which to perform the query. +has_one :through+ associations were discussed in detail earlier in this guide. h6. +:validate+ If you set the +:validate+ option to +true+, then associated objects will be validated whenever you save this object. By default, this is +false+: associated objects will not be validated when this object is saved. -h5. How to know whether there's an associated object? +h5. How To Know Whether There's an Associated Object? To know whether there's and associated object just check association.nil?: @@ -968,7 +968,7 @@ if @supplier.account.nil? end -h5. When are objects saved? +h5. When are Objects Saved? When you assign an object to a +has_one+ association, that object is automatically saved (in order to update its foreign key). In addition, any object being replaced is also automatically saved, because its foreign key will change too. @@ -978,11 +978,11 @@ If the parent object (the one declaring the +has_one+ association) is unsaved (t If you want to assign an object to a +has_one+ association without saving the object, use the association.build method. -h4. +has_many+ association reference +h4. +has_many+ Association Reference The +has_many+ association creates a one-to-many relationship with another model. In database terms, this association says that the other class will have a foreign key that refers to instances of this class. -h5. Methods added +h5. Methods Added When you declare a +has_many+ association, the declaring class automatically gains 13 methods related to the association: @@ -1158,7 +1158,7 @@ The +has_many+ association supports these options: h6. +:as+ -Setting the +:as+ option indicates that this is a polymorphic association, as discussed earlier in this guide. +Setting the +:as+ option indicates that this is a polymorphic association, as discussed earlier in this guide. h6. +:autosave+ @@ -1210,7 +1210,7 @@ NOTE: This option is ignored when you use the +:through+ option on the associati h6. +:extend+ -The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail later in this guide. +The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail later in this guide. h6. +:finder_sql+ @@ -1323,7 +1323,7 @@ The +:source_type+ option specifies the source association type for a +has_many h6. +:through+ -The +:through+ option specifies a join model through which to perform the query. +has_many :through+ associations provide a way to implement many-to-many relationships, as discussed earlier in this guide. +The +:through+ option specifies a join model through which to perform the query. +has_many :through+ associations provide a way to implement many-to-many relationships, as discussed earlier in this guide. h6. +:uniq+ @@ -1333,7 +1333,7 @@ h6. +:validate+ If you set the +:validate+ option to +false+, then associated objects will not be validated whenever you save this object. By default, this is +true+: associated objects will be validated when this object is saved. -h5. When are objects saved? +h5. When are Objects Saved? When you assign an object to a +has_many+ association, that object is automatically saved (in order to update its foreign key). If you assign multiple objects in one statement, then they are all saved. @@ -1343,11 +1343,11 @@ If the parent object (the one declaring the +has_many+ association) is unsaved ( If you want to assign an object to a +has_many+ association without saving the object, use the collection.build method. -h4. +has_and_belongs_to_many+ association reference +h4. +has_and_belongs_to_many+ Association Reference The +has_and_belongs_to_many+ association creates a many-to-many relationship with another model. In database terms, this associates two classes via an intermediate join table that includes foreign keys referring to each of the classes. -h5. Methods added +h5. Methods Added When you declare a +has_and_belongs_to_many+ association, the declaring class automatically gains 13 methods related to the association: @@ -1391,7 +1391,7 @@ assemblies.build(attributes = {}, ...) assemblies.create(attributes = {}) -h6. Additional column methods +h6. Additional Column Methods If the join table for a +has_and_belongs_to_many+ association has additional columns beyond the two foreign keys, these columns will be added as attributes to records retrieved via that association. Records returned with additional attributes will always be read-only, because Rails cannot save changes to those attributes. @@ -1589,7 +1589,7 @@ Normally Rails automatically generates the proper SQL to remove links between th h6. +:extend+ -The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail later in this guide. +The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail later in this guide. h6. +:finder_sql+ @@ -1670,7 +1670,7 @@ h6. +:validate+ If you set the +:validate+ option to +false+, then associated objects will not be validated whenever you save this object. By default, this is +true+: associated objects will be validated when this object is saved. -h5. When are objects saved? +h5. When are Objects Saved? When you assign an object to a +has_and_belongs_to_many+ association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved. @@ -1680,7 +1680,7 @@ If the parent object (the one declaring the +has_and_belongs_to_many+ associatio If you want to assign an object to a +has_and_belongs_to_many+ association without saving the object, use the collection.build method. -h4. Association callbacks +h4. Association Callbacks Normal callbacks hook into the lifecycle of Active Record objects, allowing you to work with those objects at various points. For example, you can use a +:before_save+ callback to cause something to happen just before an object is saved. @@ -1724,7 +1724,7 @@ end If a +before_add+ callback throws an exception, the object does not get added to the collection. Similarly, if a +before_remove+ callback throws an exception, the object does not get removed from the collection. -h4. Association extensions +h4. Association Extensions You're not limited to the functionality that Rails automatically builds into association proxy objects. You can also extend these objects through anonymous modules, adding new finders, creators, or other methods. For example: diff --git a/vendor/rails/railties/guides/source/caching_with_rails.textile b/vendor/rails/railties/guides/source/caching_with_rails.textile index b1c1af8b..5c555382 100644 --- a/vendor/rails/railties/guides/source/caching_with_rails.textile +++ b/vendor/rails/railties/guides/source/caching_with_rails.textile @@ -4,6 +4,13 @@ Everyone caches. This guide will teach you what you need to know about avoiding that expensive round-trip to your database and returning what you need to return to those hungry web clients in the shortest time possible. +After reading this guide, you should be able to use and configure: + +* Page, action, and fragment caching +* Sweepers +* Alternative cache stores +* Conditional GET support + endprologue. h3. Basic Caching @@ -13,8 +20,7 @@ provides by default without the use of any third party plugins. To get started make sure +config.action_controller.perform_caching+ is set to +true+ for your environment. This flag is normally set in the -corresponding config/environments/*.rb and caching is disabled by default -there for development and test, and enabled for production. +corresponding config/environments/*.rb. By default, caching is disabled for development and test, and enabled for production. config.action_controller.perform_caching = true @@ -29,9 +35,9 @@ applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with. -So, how do you enable this super-fast cache behavior? Simple, let's say you -have a controller called ProductsController and a 'list' action that lists all -the products +So, how do you enable this super-fast cache behavior? Suppose you +have a controller called +ProductsController+ and an +index+ action that lists all +the products. You could enable caching for this action like this: class ProductsController < ActionController @@ -44,34 +50,33 @@ end The first time anyone requests products/index, Rails will generate a file -called +index.html+ and the webserver will then look for that file before it -passes the next request for products/index to your Rails application. +called +index.html+. If a web server see this file, it will be served in response to the +next request for products/index, without your Rails application being called. By default, the page cache directory is set to Rails.public_path (which is -usually set to +RAILS_ROOT + "/public"+) and this can be configured by +usually set to +File.join(self.root, "public")+ - that is, the public directory under your Rails application's root). This can be configured by changing the configuration setting +config.action_controller.page_cache_directory+. Changing the default from /public helps avoid naming conflicts, since you may want to put other static html in /public, but changing this will require web server reconfiguration to let the web server know where to serve the cached files from. -The Page Caching mechanism will automatically add a +.html+ extension to +The page caching mechanism will automatically add a +.html+ extension to requests for pages that do not have an extension to make it easy for the -webserver to find those pages and this can be configured by changing the +webserver to find those pages. This can be configured by changing the configuration setting +config.action_controller.page_cache_extension+. -In order to expire this page when a new product is added we could extend our -example controller like this: +In order to expire this page when a new product is added you could extend the products controller like this: class ProductsController < ActionController - caches_page :list + caches_page :index - def list; end + def index; end def create - expire_page :action => :list + expire_page :action => :index end end @@ -80,19 +85,19 @@ 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, so /products/list?page=1 will be written out to the filesystem as /products/list.html and if someone requests /products/list?page=2, they will be returned the same result as page=1, so be careful when page caching GET parameters in the URL! +Note: Page caching ignores all parameters, so /products/list?page=1 will be written out to the filesystem as /products/list.html and if someone requests /products/list?page=2, they will be returned the same result as page=1. Be careful when page caching GET parameters in the URL! 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, so that -authentication and other restrictions can be used while still serving the +One of the issues with page caching is that you cannot use it for pages that +require checking code to determine whether the user should be permitted access. 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 web server to the Rails stack and Action Pack so +that before filters can be run on it before the cache is served. This allows you to use +authentication and other restrictions 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 the exact same way as with page caching. Let's say you only wanted authenticated users to edit or create a Product object, but still cache those pages: @@ -101,13 +106,13 @@ object, but still cache those pages: class ProductsController < ActionController before_filter :authenticate, :only => [ :edit, :create ] - caches_page :list + caches_page :index caches_action :edit - def list; end + def index; end def create - expire_page :action => :list + expire_page :action => :index expire_action :action => :edit end @@ -116,19 +121,19 @@ class ProductsController < ActionController end -And you can also use +:if+ (or +:unless+) to pass a Proc that specifies when the +You can also use +:if+ (or +:unless+) to pass a Proc that specifies when the action should be cached. Also, you can use +:layout => false+ to cache without -layout so that dynamic information in the layout such as logged in user info +layout so that dynamic information in the layout such as the name of the logged-in user or the number of items in the cart can be left uncached. This feature is available as of Rails 2.2. You can modify the default action cache path by passing a +:cache_path+ option. -This will be passed directly to ActionCachePath.path_for. This is handy for +This will be passed directly to +ActionCachePath.path_for+. 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. Finally, if you are using memcached, you can also pass +:expires_in+. In fact, -all parameters not used by caches_action are sent to the underlying cache +all parameters not used by +caches_action+ are sent to the underlying cache store. h4. Fragment Caching @@ -162,52 +167,56 @@ could use this piece of code: The cache block in our example will bind to the action that called it and is -written out to the same place as the Action Cache, which means that if you +written out to the same place as the action cache, which means that if you want to cache multiple fragments per action, you should provide an +action_suffix+ to the cache call: -<% cache(:action => 'recent', :action_suffix => 'all_products') do %> +<% cache(:action => 'recent', :action_suffix => 'all_prods') do %> All available products: -and you can expire it using the +expire_fragment+ method, like so: +You can expire the cache using the +expire_fragment+ method, like so: -expire_fragment(:controller => 'products', :action => 'recent', :action_suffix => 'all_products) +expire_fragment(:controller => 'products', :action => 'recent', + :action_suffix => 'all_prods) -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 +If you don't want the cache block to bind to the action that called it, you can +also use globally keyed fragments. To do this, call the +cache+ method with a key, like so: -<% cache(:key => ['all_available_products', @latest_product.created_at].join(':')) do %> +<% cache(:key => + ['all_available_products', @latest_product.created_at].join(':')) do %> All available products: <% end %> -This fragment is then available to all actions in the ProductsController using +This fragment is then available to all actions in the +ProductsController+ using the key and can be expired the same way: -expire_fragment(:key => ['all_available_products', @latest_product.created_at].join(':')) +expire_fragment(:key => + ['all_available_products', @latest_product.created_at].join(':')) h4. Sweepers Cache sweeping is a mechanism which allows you to get around having a ton of -expire_{page,action,fragment} calls in your code by moving all the work -required to expire cached content into a +ActionController::Caching::Sweeper+ -class that is an Observer and looks for changes to an object via callbacks, -and when a change occurs it expires the caches associated with that object n ++expire_{page,action,fragment}+ calls in your code. It does this by moving all the work +required to expire cached content into na +ActionController::Caching::Sweeper+ +class. This class is an Observer that looks for changes to an object via callbacks, +and when a change occurs it expires the caches associated with that object in an around or after filter. Continuing with our Product controller example, we could rewrite it with a -sweeper such as the following: +sweeper like this: class StoreSweeper < ActionController::Caching::Sweeper - observe Product # This sweeper is going to keep an eye on the Product model + # This sweeper is going to keep an eye on the Product model + observe Product # If our sweeper detects that a Product was created call this def after_create(product) @@ -230,13 +239,13 @@ class StoreSweeper < ActionController::Caching::Sweeper expire_page(:controller => '#{record}', :action => 'list') # Expire a fragment - expire_fragment(:controller => '#{record}', :action => 'recent', :action_suffix => 'all_products') + expire_fragment(:controller => '#{record}', + :action => 'recent', :action_suffix => 'all_products') end end -Then we add it to our controller to tell it to call the sweeper when certain -actions are called. So, if we wanted to expire the cached content for the +The sweeper has to be added to the controller that will use it. So, if we wanted to expire the cached content for the list and edit actions when the create action was called, we could do the following: @@ -263,9 +272,9 @@ end 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 +query. If Rails encounters the same query again during the current request, it will used the cached result set as opposed to running the query against the -database again. +database. For example: @@ -304,9 +313,9 @@ database again the second time that finder is called. Query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action. -h4. Cache stores +h4. Cache Stores -Rails (as of 2.1) provides different stores for the cached data for action and +Rails (as of 2.1) provides different stores for the cached data created by action and fragment caches. Page caches are always stored on disk. Rails 2.1 and above provide ActiveSupport::Cache::Store which can be used to @@ -314,7 +323,7 @@ cache strings. Some cache store implementations, like MemoryStore, are able to cache arbitrary Ruby objects, but don't count on every cache store to be able to do that. -The default cache stores provided include: +The default cache stores provided with Rails include: 1) ActiveSupport::Cache::MemoryStore: A cache store implementation which stores everything into memory in the same process. If you're running multiple Ruby on @@ -335,13 +344,12 @@ need thread-safety. ActionController::Base.cache_store = :memory_store -2) ActiveSupport::Cache::FileStore: Cached data is stored on the disk, this is +2) ActiveSupport::Cache::FileStore: Cached data is stored on the disk. This is the default store and the default path for this store is: /tmp/cache. Works well for all types of environments and allows all processes running from the same application directory to access the cached content. If /tmp/cache does not exist, the default store becomes MemoryStore. - ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" @@ -351,7 +359,6 @@ DRb process that all servers communicate with. This works for all environments and only keeps one cache around for all processes, but requires that you run and manage a separate DRb process. - ActionController::Base.cache_store = :drb_store, "druby://localhost:9192" @@ -361,27 +368,28 @@ Rails uses the bundled memcached-client gem by default. This is currently the most popular cache store for production websites. Special features: - * Clustering and load balancing. One can specify multiple memcached servers, + +* Clustering and load balancing. One can specify multiple memcached servers, and MemCacheStore will load balance between all available servers. If a server goes down, then MemCacheStore will ignore it until it goes back online. - * Time-based expiry support. See write and the +:expires_in+ option. - * Per-request in memory cache for all communication with the MemCache server(s). +* Time-based expiry support. See +write+ and the +:expires_in+ option. +* Per-request in memory cache for all communication with the MemCache server(s). It also accepts a hash of additional options: - * +:namespace+- specifies a string that will automatically be prepended to keys when accessing the memcached store. - * +:readonly+- a boolean value that when set to true will make the store read-only, with an error raised on any attempt to write. - * +:multithread+ - a boolean value that adds thread safety to read/write operations - it is unlikely you'll need to use this option as the Rails threadsafe! method offers the same functionality. +* +:namespace+- specifies a string that will automatically be prepended to keys when accessing the memcached store. +* +:readonly+- a boolean value that when set to true will make the store read-only, with an error raised on any attempt to write. +* +:multithread+ - a boolean value that adds thread safety to read/write operations - it is unlikely you'll need to use this option as the Rails threadsafe! method offers the same functionality. The read and write methods of the MemCacheStore accept an options hash too. When reading you can specify +:raw => true+ to prevent the object being marshaled (by default this is false which means the raw value in the cache is passed to -Marshal.load before being returned to you.) ++Marshal.load+ before being returned to you.) -When writing to the cache it is also possible to specify +:raw => true+ means -the value is not passed to Marshal.dump before being stored in the cache (by +When writing to the cache it is also possible to specify +:raw => true+. This means +that the value is not passed to +Marshal.dump+ before being stored in the cache (by default this is false). The write method also accepts an +:unless_exist+ flag which determines whether @@ -416,11 +424,11 @@ ActionController::Base.cache_store = :compressed_mem_cache_store, "localhost" ActionController::Base.cache_store = MyOwnStore.new("parameter") -+Note: config.cache_store can be used in place of -ActionController::Base.cache_store in your Rails::Initializer.run block in -environment.rb+ +NOTE: +config.cache_store+ can be used in place of ++ActionController::Base.cache_store+ in the +Rails::Initializer.run+ block in +environment.rb. -In addition to all of this, Rails also adds the ActiveRecord::Base#cache_key +In addition to all of this, Rails also adds the +ActiveRecord::Base#cache_key+ method that generates a key using the class name, id and updated_at timestamp (if available). @@ -432,9 +440,9 @@ Rails.cache.write("city", "Duckburgh") Rails.cache.read("city") # => "Duckburgh" -h3. Conditional GET support +h3. Conditional GET Support -Conditional GETs are a facility of the HTTP spec that provide a way for web +Conditional GETs are a feature of the HTTP specification that provide a way for web servers to tell browsers that the response to a GET request hasn't changed since the last request and can be safely pulled from the browser cache. diff --git a/vendor/rails/railties/guides/source/command_line.textile b/vendor/rails/railties/guides/source/command_line.textile index 078eefd9..d0424584 100644 --- a/vendor/rails/railties/guides/source/command_line.textile +++ b/vendor/rails/railties/guides/source/command_line.textile @@ -24,7 +24,7 @@ There are a few commands that are absolutely critical to your everyday usage of Let's create a simple Rails application to step through each of these commands in context. -h4. rails +h4. +rails+ The first thing we'll want to do is create a new Rails application by running the +rails+ command after installing Rails. @@ -48,7 +48,7 @@ Rails will set you up with what seems like a huge amount of stuff for such a tin INFO: This output will seem very familiar when we get to the +generate+ command. Creepy foreshadowing! -h4. server +h4. +server+ Let's try it! The +server+ command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to view your work through a web browser. @@ -71,7 +71,7 @@ WHOA. With just three commands we whipped up a Rails server listening on port 30 See? Cool! It doesn't do much yet, but we'll change that. -h4. generate +h4. +generate+ The +generate+ command uses templates to create a whole lot of things. You can always find out what's available by running +generate+ by itself. Let's do that: @@ -248,15 +248,15 @@ Let's see the interface Rails created for us. ./script/server; http://localhost: We can create new high scores (55,160 on Space Invaders!) -h4. console +h4. +console+ The +console+ command lets you interact with your Rails application from the command line. On the underside, +script/console+ uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website. -h4. dbconsole +h4. +dbconsole+ +dbconsole+ figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3. -h4. plugin +h4. +plugin+ The +plugin+ command simplifies plugin management; think a miniature version of the Gem utility. Let's walk through installing a plugin. You can call the sub-command *discover*, which sifts through repositories looking for plugins, or call *source* to add a specific repository of plugins, or you can specify the plugin location directly. @@ -272,7 +272,7 @@ $ ./script/plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_ ... -h4. runner +h4. +runner+ runner runs Ruby code in the context of Rails non-interactively. For instance: @@ -280,7 +280,7 @@ h4. runner $ ./script/runner "Model.long_running_method" -h4. destroy +h4. +destroy+ Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it. Believe you-me, the creation of this tutorial used this command many times! @@ -309,7 +309,7 @@ $ ./script/destroy model Oops notempty app -h4. about +h4. +about+ Check it: Version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version! +about+ is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation. @@ -335,7 +335,7 @@ h3. The Rails Advanced Command Line The more advanced uses of the command line are focused around finding useful (even surprising at times) options in the utilities, and fitting utilities to your needs and specific work flow. Listed here are some tricks up Rails' sleeve. -h4. Rails with databases and SCM +h4. Rails with Databases and SCM When creating a new Rails application, you have the option to specify what kind of database and what kind of source code management system your application is going to use. This will save you a few minutes, and certainly many keystrokes. @@ -393,7 +393,7 @@ development: It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the +rails+ command to generate the basis of your app. -h4. server with different backends +h4. +server+ with Different Backends Many people have created a large number different web servers in Ruby, and many of them can be used to run Rails. Since version 2.3, Rails uses Rack to serve its webpages, which means that any webserver that implements a Rack handler can be used. This includes WEBrick, Mongrel, Thin, and Phusion Passenger (to name a few!). @@ -534,25 +534,25 @@ rake tmp:sockets:clear # Clears all files in tmp/sockets Let's take a look at some of these 80 or so rake tasks. -h5. db: Database +h5. +db:+ Database The most common tasks of the +db:+ Rake namespace are +migrate+ and +create+, and it will pay off to try out all of the migration rake tasks (+up+, +down+, +redo+, +reset+). +rake db:version+ is useful when troubleshooting, telling you the current version of the database. -h5. doc: Documentation +h5. +doc:+ Documentation If you want to strip out or rebuild any of the Rails documentation (including this guide!), the +doc:+ namespace has the tools. Stripping documentation is mainly useful for slimming your codebase, like if you're writing a Rails application for an embedded platform. -h5. gems: Ruby gems +h5. +gems:+ Ruby gems You can specify which gems your application uses, and +rake gems:install+ will install them for you. Look at your environment.rb to learn how with the *config.gem* directive. NOTE: +gems:unpack+ will unpack, that is internalize your application's Gem dependencies by copying the Gem code into your vendor/gems directory. By doing this you increase your codebase size, but simplify installation on new hosts by eliminating the need to run +rake gems:install+, or finding and installing the gems your application uses. -h5. notes: Code note enumeration +h5. +notes:+ Code note enumeration These tasks will search through your code for commented lines beginning with "FIXME", "OPTIMIZE", "TODO", or any custom annotation (like XXX) and show you them. -h5. rails: Rails-specific tasks +h5. +rails:+ Rails-specific tasks In addition to the +gems:unpack+ task above, you can also unpack the Rails backend specific gems into vendor/rails by calling +rake rails:freeze:gems+, to unpack the version of Rails you are currently using, or +rake rails:freeze:edge+ to unpack the most recent (cutting, bleeding edge) version. @@ -560,7 +560,7 @@ When you have frozen the Rails gems, Rails will prefer to use the code in vendor After upgrading Rails, it is useful to run +rails:update+, which will update your config and scripts directories, and upgrade your Rails-specific javascript (like Scriptaculous). -h5. test: Rails tests +h5. +test:+ Rails tests INFO: A good description of unit testing in Rails is given in "A Guide to Testing Rails Applications":testing.html @@ -568,15 +568,15 @@ Rails comes with a test suite called Test::Unit. It is through the use of tests The +test:+ namespace helps in running the different tests you will (hopefully!) write. -h5. time: Timezones +h5. +time:+ Timezones You can list all the timezones Rails knows about with +rake time:zones:all+, which is useful just in day-to-day life. -h5. tmp: Temporary files +h5. +tmp:+ Temporary files The tmp directory is, like in the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. The +tmp:+ namespace tasks will help you clear them if you need to if they've become overgrown, or create them in case of an +rm -rf *+ gone awry. -h5. Miscellaneous tasks +h5. Miscellaneous Tasks +rake stats+ is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio. @@ -584,3 +584,6 @@ h5. Miscellaneous tasks +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. +h3. Changelog + +"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/29 diff --git a/vendor/rails/railties/guides/source/contribute.textile b/vendor/rails/railties/guides/source/contribute.textile index 48f1a51d..650004bd 100644 --- a/vendor/rails/railties/guides/source/contribute.textile +++ b/vendor/rails/railties/guides/source/contribute.textile @@ -7,12 +7,12 @@ endprologue. h3. How to Contribute? * We have an open commit policy: anyone is welcome to contribute, but you'll need to ask for commit access. -* PM lifo at "GitHub":http://github.om asking for "docrails":http://github.com/lifo/docrails commit access. +* PM lifo at "GitHub":http://github.com asking for "docrails":http://github.com/lifo/docrails/tree/master commit access. * Guides are written in Textile, and reside at railties/guides/source in the docrails project. * All images are in the railties/guides/images directory. * Sample format : "Active Record Associations":http://github.com/lifo/docrails/blob/3e56a3832415476fdd1cb963980d0ae390ac1ed3/railties/guides/source/association_basics.textile * Sample output : "Active Record Associations":http://guides.rails.info/association_basics.html -* You can build the Guides during testing by running railties/guides/rails_guides.rb. +* You can build the Guides during testing by running +rake guides+ in the +railties+ directory. h3. What to Contribute? @@ -44,26 +44,21 @@ For each completed guide, the lead contributor will receive all of the following h3. Rules -* Guides are licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 License. +* Guides are licensed under a Creative Commons Attribution-Share Alike 3.0 License. * If you're not sure whether a guide is actively being worked on, stop by IRC and ask. * If the same guide writer wants to write multiple guides, that's ideally the situation we'd love to be in! However, that guide writer will only receive the cash prize for all the subsequent guides (and not the GitHub or RPM prizes). * Our review team will have the final say on whether the guide is complete and of good enough quality. -h3. Reviewers - -These are the main reviewers and editors for the guides: - -* Hongli Lai -* Mike Gunderloy -* Pratik Naik -* Xavier Noria - All authors should read and follow the "Rails Guides Conventions":http://wiki.github.com/lifo/docrails/rails-guides-conventions and the "Rails API Documentation Conventions":http://wiki.github.com/lifo/docrails/rails-api-documentation-conventions. h3. Translations The translation effort for the Rails Guides is just getting underway. We know about projects to translate the Guides into Spanish, Portuguese, Polish, and French. For more details or to get involved see the "Translating Rails Guides":http://wiki.github.com/lifo/docrails/translating-rails-guides page. +h3. Mailing List + +"Ruby on Rails: Documentation":http://groups.google.com/group/rubyonrails-docs is the mailing list for all the guides/documentation related discussions. + h3. IRC Channel ==#docrails @ irc.freenode.net== @@ -72,6 +67,5 @@ h3. Contact If you have any questions or need any clarification, feel free to contact: -* IRC : lifo, mikeg1a, fxn, or FooBarWidget in #docrails +* IRC : lifo, mikeg1a or fxn in #docrails * Email : pratiknaik aT gmail - diff --git a/vendor/rails/railties/guides/source/contributing_to_rails.textile b/vendor/rails/railties/guides/source/contributing_to_rails.textile new file mode 100644 index 00000000..84778ed9 --- /dev/null +++ b/vendor/rails/railties/guides/source/contributing_to_rails.textile @@ -0,0 +1,239 @@ +h2. Contributing to Rails + +This guide covers ways in which _you_ can become a part of the ongoing development of Rails. After reading it, you should be familiar with: + +* Using Lighthouse to report issues with Rails +* Cloning edge Rails and running the test suite +* Helping to resolve existing issues +* Contributing to the Rails documentation +* Contributing to the Rails code + +Rails is not "someone else's framework." Over the years, hundreds of people have contributed code ranging from a single character to massive architectural changes, all with the goal of making Rails better for everyone. Even if you don't feel up to writing code yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches to contributing documentation. + +endprologue. + +h3. Reporting a Rails Issue + +Rails uses a "Lighthouse project":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/ to track issues (primarily bugs and contributions of new code). If you've found a bug in Rails, this is the place to start. + +NOTE: Bugs in the most recent released version of 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 Rails, you can start by "adding a new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new to the Rails Lighthouse. At the minimum, your ticket 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. + +You shouldn't assign the bug to a particular core developer (through the *Who's Responsible* select list) unless you know for sure which developer will be handling any patch. The core team periodically reviews issues and assigns developers and milestones to them. + +You should set tags for your issue. Use the "bug" tag for a bug report, and add the "patch" tag if you are attaching a patch. Try to find some relevant tags from the existing tag list (which will appear as soon as you start typing in the *Choose some tags* textbox), rather than creating new tags. + +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 ticket in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the ticket automatically will see any activity or that others will jump to fix it. Creating a ticket 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. + +h4. Special Treatment for Security Issues + +If you've found a security vulnerability in Rails, please do *not* report it via a Lighthouse ticket. Lighthouse tickets are public as soon as they are entered. Instead, you should use the dedicated email address "security@rubyonrails.org":mailto:security@rubyonrails.org to report any vulnerabilities. This alias is monitored and the core team will work with you to quickly and completely address any such vulnerabilities. + +h4. What About Feature Requests? + +Please don't put "feature request" tickets into Lighthouse. If there's a new feature that you want to see added to 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 Rails. If you enter a wishlist item in Lighthouse with no code, you can expect it to be marked "invalid" as soon as it's reviewed. + +h3. Running the Rails Test Suite + +To move on from submitting bugs to helping resolve existing issues or contributing your own code to Rails, you _must_ be able to run the Rails test suite. In this section of the guide you'll learn how to set up the tests on your own computer. + +h4. Install git + +Rails uses git for source code control. You won’t be able to do anything without the Rails source code, and this is a prerequisite. The "git homepage":http://git-scm.com/ has installation instructions. If you’re on OS X, use the "Git for OS X":http://code.google.com/p/git-osx-installer/ installer. If you're unfamiliar with git, there are a variety of resources on the net that will help you learn more: + +* "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/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://github.com/guides/home offers links to a variety of git resources. + +h4. Get the Rails Source Code + +Don’t fork the main Rails repository. Instead, you want to clone it to your own computer. Navigate to the folder where you want the source code (it will create its own /rails subdirectory) and run: + + +git clone git://github.com/rails/rails.git +cd rails + + +h4. Set up and Run the Tests + +All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. For the tests that touch the database, this means creating the databases. If you're using MySQL: + + +mysql> create database activerecord_unittest; +mysql> create database activerecord_unittest2; +mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.* + to 'rails'@'localhost'; +mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* + to 'rails'@'localhost'; + + +If you’re using another database, check the files under +activerecord/test/connections+ in the Rails source code 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. + +Now if you go back to the root of the Rails source on your machine and run +rake+ with no parameters, you should see every test in all of the Rails components pass. If you want to run the all ActiveRecord tests (or just a single one) with another database adapter, enter this from the +activerecord+ directory: + + +rake test_sqlite3 +rake test_sqlite3 TEST=test/cases/validations_test.rb + + +You can change +sqlite3+ with +jdbcmysql+, +jdbcsqlite3+, +jdbcpostgresql+, +mysql+ or +postgresql+. 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 Rails continuous integration server runs. + + + +NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, SQLite 2, and SQLite 3. Subtle differences between the various Active Record database adapters have been behind the rejection of many patches that looked OK when tested only against MySQL. + +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 "open tickets":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets?q=state%3Aopen list in Lighthouse, you'll find hundreds 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 ticket 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 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 Rails source: looking at the existing test files will teach you how to write more tests for Rails. New tests are best contributed in the form of a patch, as explained later on in the "Contributing to the Rails Code" section. + +Anything you can do to make bug reports more succinct or easier to reproduce is a help to folks trying to write code to fix those bugs - whether you end up writing the code yourself or not. + +h4. Testing Patches + +You can also help out by examining patches that have been submitted to Rails via Lighthouse. To apply someone's changes you need to first create a branch of the Rails source code: + + +git checkout -b testing_branch + + +Then you can apply their patch: + + +git am < their-patch-file.diff + + +After applying a patch, test it out! Here are some things to think about: + +* Does the patch actually work? +* Are you happy with the tests? Can you follow what they're testing? Are there any tests missing? +* Does the documentation still seem right to you? +* 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 patch contains a good change, comment on the Lighthouse ticket indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like: + +
+I like the way you've restructured that code in generate_finder_sql, much nicer. The tests look good too. +
+ +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 patch. Once three people have approved it, add the "verified" tag. This will bring it to the attention of a core team member who will review the changes looking for the same kinds of things. + +h3. Contributing to the Rails Documentation + +Another area where you can help out if you're not yet ready to take the plunge to writing Rails core code is with Rails documentation. You can help with the Rails Guides or the Rails API documentation. + +TIP: "docrails":http://github.com/lifo/docrails/tree/master is the documentation branch for Rails with an *open commit policy*. You can simply PM "lifo":http://github.com/lifo on Github and ask for the commit rights. Documentation changes made as part of the "docrails":http://github.com/lifo/docrails/tree/master project, are merged back to the Rails master code from time to time. Check out the "original announcement":http://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch for more details. + +h4. The Rails Guides + +The "Rails Guides":http://guides.rubyonrails.org/ are a set of online resources that are designed to make people productive with Rails and to understand how all of the pieces fit together. These guides (including this one!) are written as part of the "docrails":http://github.com/lifo/docrails/tree/master project. If you have an idea for a new guide, or improvements for an existing guide, you can refer to the "contribution page":contribute.html for instructions on getting involved. + +h4. The Rails API Documentation + +The "Rails API documentation":http://api.rubyonrails.org/ is automatically generated from the Rails source code via "RDoc":http://rdoc.rubyforge.org/. If you find some part of the documentation to be incomplete, confusing, or just plain wrong, you can step in and fix it. + +To contribute an update to the API documentation, you can contact "lifo":http://github.com/lifo on GitHub and ask for commit rights to the docrails repository and push your changes to the docrails repository. Please follow the "docrails RDoc conventions":http://wiki.github.com/lifo/docrails/rails-api-documentation-conventions when contributing the changes. + +h3. The Rails Wiki + +The "Rails wiki":http://wiki.rubyonrails.org/ is a collection of user-generated and freely-editable information about Rails. It covers everything from getting started to FAQs to how-tos and popular plugins. To contribute to the wiki, just find some useful information that isn't there already and add it. There are style guidelines to help keep the wiki a coherent resources; see the section on "contributing to the wiki":http://wiki.rubyonrails.org/#contributing_to_the_wiki for more details. + +h3. Contributing to the Rails Code + +When you're ready to take the plunge, one of the most helpful ways to contribute to Rails is to actually submit source code. Here's a step-by-step listing of the things you need to do to make this a successful experience. + +h4. Learn the Language and the Framework + +Learn at least _something_ about Ruby and Rails. If you don’t understand the syntax of the language, common Ruby idioms, and the code that already exists in Rails, you’re unlikely to be able to build a good patch (that is, one that will get accepted). You don’t have to know every in-and-out of the language and the framework; some of the Rails code is fiendishly complex. But Rails is probably not appropriate as the first place that you ever write Ruby code. You should at least understand (though not necessarily memorize) "The Ruby Programming Language":http://www.amazon.com/gp/product/0596516177?ie=UTF8&linkCode=as2&camp=1789&creative=390957&creativeASIN=0596516177 and have browsed the Rails source code. + +h4. Fork the Rails Source Code + +Fork Rails. You’re not going to put your patches right into the master branch, OK? This is where you need that copy of Rails that you cloned earlier. Think of a name for your new branch and run + + +git checkout -b my_new_branch + + +It doesn’t really matter what name you use, because this branch will only exist on your local computer. + +h4. Write Your Code + +Now get busy and add your code to Rails (or edit the existing 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 + +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. + +h4. Commit Your Changes + +When you're happy with the code on your computer, you need to commit the changes to git: + + +git commit -a -m "Here is a commit message" + + +h4. Update Rails + +Update your copy of Rails. It’s pretty likely that other changes to core Rails have happened while you were working. Go get them: + + +git checkout master +git pull + + +Now reapply your patch on top of the latest changes: + + +git checkout my_new_branch +git rebase master + + +No conflicts? Tests still pass? Change still seems reasonable to you? Then move on. + +h4. Create a Patch + +Now you can create a patch file to share with other developers (and with the Rails core team). Still in your branch, run + + +git commit -a +git format-patch master --stdout > my_new_patch.diff + + +Sanity check the results of this operation: open the diff file in your text editor of choice and make sure that no unintended changes crept in. + +h4. Create a Lighthouse Ticket + +Now create a ticket with your patch. Go to the "new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new page at Lighthouse. Fill in a reasonable title and description, remember to attach your patch file, and tag the ticket with the ‘patch’ tag and whatever other subject area tags make sense. + +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 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. + +And then...think about your next contribution! + +h3. Changelog + +"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/64 + +* March 2, 2009: Initial draft by "Mike Gunderloy":credits.html#mgunderloy + + diff --git a/vendor/rails/railties/guides/source/credits.erb.textile b/vendor/rails/railties/guides/source/credits.erb.textile index 441ea60f..b09a931f 100644 --- a/vendor/rails/railties/guides/source/credits.erb.textile +++ b/vendor/rails/railties/guides/source/credits.erb.textile @@ -5,6 +5,28 @@ p. We'd like to thank the following people for their tireless contributions to t <% end %> +

Rails Documentation Team

+ +<% author('Mike Gunderloy', 'mgunderloy') do %> + Mike Gunderloy is a consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails activism team":http://rubyonrails.org/activists . He brings 25 years of experience in a variety of languages to bear on his current work with Rails. His near-daily links and other blogging can be found at "A Fresh Cup":http://afreshcup.com and he "twitters":http://twitter.com/MikeG1 too much. +<% end %> + +<% author('Pratik Naik', 'lifo') do %> + Pratik Naik is a Ruby on Rails consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails core team":http://rubyonrails.org/core. He maintains a blog at "has_many :bugs, :through => :rails":http://m.onkey.org and has an active "twitter account":http://twitter.com/lifo. +<% end %> + +<% author('Xavier Noria', 'fxn', 'fxn.jpg') do %> + Xavier Noria has been around dynamic languages since 2000. He fell in love with Rails in 2005, and cofounded Rails-based software company ASPgems in mid-2006. Xavier is president of the Spanish Ruby Users Group and has been involved in Rails in several ways. He enjoys combining his passion for Rails and his past life as a proofreader of math textbooks. Oh, he also "tweets":http://twitter.com/fxn! +<% end %> + +

Rails Guides Designers

+ +<% author('Jason Zimdars', 'jz') do %> + Jason Zimdars is an experienced creative director and web designer who has lead UI and UX design for numerous websites and web applications. You can see more of his design and writing at Thinkcage.com or follow him on Twitter. +<% end %> + +

Rails Guides Authors

+ <% author('Frederick Cheung', 'fcheung') do %> Frederick Cheung is Chief Wizard at Texperts where he has been using Rails since 2006. He is based in Cambridge (UK) and when not consuming fine ales he blogs at "spacevatican.org":http://www.spacevatican.org. <% end %> @@ -17,23 +39,14 @@ p. We'd like to thank the following people for their tireless contributions to t Jeff Dean is a software engineer with "Pivotal Labs":http://pivotallabs.com. <% end %> -<% author('Mike Gunderloy', 'mgunderloy') do %> - Mike Gunderloy is a consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails activism team":http://rubyonrails.org/activists . He brings 25 years of experience in a variety of languages to bear on his current work with Rails. His near-daily links and other blogging can be found at "A Fresh Cup":http://afreshcup.com and he "twitters":http://twitter.com/MikeG1 too much. -<% end %> - <% author('Cássio Marques', 'cmarques') do %> Cássio Marques is a Brazilian software developer working with different programming languages such as Ruby, JavaScript, CPP and Java, as an independent consultant. He blogs at "/* CODIFICANDO */":http://cassiomarques.wordpress.com, which is mainly written in Portuguese, but will soon get a new section for posts with English translation. <% end %> -<% author('Pratik Naik', 'lifo') do %> - Pratik Naik is a Ruby on Rails consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails core team":http://rubyonrails.com/core. He maintains a blog at "has_many :bugs, :through => :rails":http://m.onkey.org and has an active "twitter account":http://twitter.com/lifo. - <% author('Emilio Tagua', 'miloops') do %> - Emilio Tagua -- a.k.a. miloops -- is an Argentinian entrepreneur, developer, open source contributor and Rails evangelist. Cofounder of "Eventioz":http://www.eventioz.com. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as miloops. + Emilio Tagua -- a.k.a. miloops -- is an Argentinian entrepreneur, developer, open source contributor and Rails evangelist. Cofounder of "Eventioz":http://eventioz.com. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as miloops. <% end %> <% author('Heiko Webers', 'hawe') do %> Heiko Webers is the founder of "bauland42":http://www.bauland42.de, a German web application security consulting and development company focused on Ruby on Rails. He blogs at the "Ruby on Rails Security Project":http://www.rorsecurity.info. After 10 years of desktop application development, Heiko has rarely looked back. <% end %> - -<% end %> diff --git a/vendor/rails/railties/guides/source/debugging_rails_applications.textile b/vendor/rails/railties/guides/source/debugging_rails_applications.textile index b1d6db2e..c059fdab 100644 --- a/vendor/rails/railties/guides/source/debugging_rails_applications.textile +++ b/vendor/rails/railties/guides/source/debugging_rails_applications.textile @@ -17,7 +17,7 @@ One common task is to inspect the contents of a variable. In Rails, you can do t * +to_yaml+ * +inspect+ -h4. debug +h4. +debug+ The +debug+ helper will return a <pre>-tag that renders the object using the YAML format. This will generate human-readable data from any object. For example, if you have this code in a view: @@ -46,7 +46,7 @@ attributes_cache: {} Title: Rails debugging guide -h4. to_yaml +h4. +to_yaml+ Displaying an instance variable, or any other object or method, in yaml format can be achieved this way: @@ -76,7 +76,7 @@ attributes_cache: {} Title: Rails debugging guide -h4. inspect +h4. +inspect+ Another useful method for displaying object values is +inspect+, especially when working with arrays or hashes. This will print the object value as a string. For example: @@ -96,7 +96,7 @@ Will be rendered as follows: Title: Rails debugging guide -h4. Debugging Javascript +h4. Debugging JavaScript Rails has built-in support to debug RJS, to active it, set +ActionView::Base.debug_rjs+ to _true_, this will specify whether RJS responses should be wrapped in a try/catch block that alert()s the caught exception (and then re-raises it). @@ -118,7 +118,7 @@ h3. The Logger It can also be useful to save information to log files at runtime. Rails maintains a separate log file for each runtime environment. -h4. What is The Logger? +h4. What is the Logger? Rails makes use of Ruby's standard +logger+ to write log information. You can also substitute another logger such as +Log4R+ if you wish. @@ -209,7 +209,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 +ruby-debug+ 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. diff --git a/vendor/rails/railties/guides/source/form_helpers.textile b/vendor/rails/railties/guides/source/form_helpers.textile index 41d8fba3..9ab4deff 100644 --- a/vendor/rails/railties/guides/source/form_helpers.textile +++ b/vendor/rails/railties/guides/source/form_helpers.textile @@ -1,4 +1,4 @@ -h2. Rails form helpers +h2. Rails Form helpers Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of form control naming and their numerous attributes. Rails deals away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use. @@ -16,7 +16,7 @@ endprologue. NOTE: This guide is not intended to be a complete documentation of available form helpers and their arguments. Please visit "the Rails API documentation":http://api.rubyonrails.org/ for a complete reference. -h3. Dealing With Basic Forms +h3. Dealing with Basic Forms The most basic form helper is +form_tag+. @@ -43,7 +43,7 @@ If you carefully observe this output, you can see that the helper generated some NOTE: Throughout this guide, this +div+ with the hidden input will be stripped away to have clearer code samples. -h4. A Generic search form +h4. A Generic Search Form Probably the most minimal form often seen on the web is a search form with a single text input for search terms. This form consists of: @@ -82,7 +82,7 @@ Besides +text_field_tag+ and +submit_tag+, there is a similar helper for _every_ TIP: For every form input, an ID attribute is generated from its name ("q" in the example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript. -h4. Multiple hashes in form helper calls +h4. Multiple Hashes in Form Helper Calls By now you've seen that the +form_tag+ helper accepts 2 arguments: the path for the action and an options hash. This hash specifies the method of form submission and HTML options such as the form element's class. @@ -104,7 +104,7 @@ This is a common pitfall when using form helpers, since many of them accept mult WARNING: Do not delimit the second hash without doing so with the first hash, otherwise your method invocation will result in an +expecting tASSOC+ syntax error. -h4. Helpers for generating form elements +h4. Helpers for Generating Form Elements Rails provides a series of helpers for generating form elements such as checkboxes, text fields, radio buttons, and so on. These basic helpers, with names ending in _tag such as +text_field_tag+, +check_box_tag+, etc., generate just a single +<input>+ element. The first parameter to these is always the name of the input. In the controller this name will be the key in the +params+ hash used to get the value entered by the user. For example, if the form contains @@ -140,7 +140,7 @@ output: The second parameter to +check_box_tag+ is the value of the input. This is the value that will be submitted by the browser if the checkbox is ticked (i.e. the value that will be present in the +params+ hash). With the above form you would check the value of +params[:pet_dog]+ and +params[:pet_cat]+ to see which pets the user owns. -h5. Radio buttons +h5. Radio Buttons Radio buttons, while similar to checkboxes, are controls that specify a set of options in which they are mutually exclusive (i.e. the user can only pick one): @@ -162,7 +162,7 @@ As with +check_box_tag+ the second parameter to +radio_button_tag+ is the value IMPORTANT: Always use labels for each checkbox and radio button. They associate text with a specific option and provide a larger clickable region. -h4. Other helpers of interest +h4. Other Helpers of Interest Other form controls worth mentioning are the text area, password input and hidden input: @@ -183,9 +183,9 @@ Hidden inputs are not shown to the user, but they hold data like any textual inp TIP: If you're using password input fields (for any purpose), you might want to prevent their values showing up in application logs by activating +filter_parameter_logging(:password)+ in your ApplicationController. -h3. Dealing With Model Objects +h3. Dealing with Model Objects -h4. Model object helpers +h4. Model Object Helpers A particularly common task for a form is editing or creating a model object. While the +*_tag+ helpers can certainly be used for this task they are somewhat verbose as for each tag you would have to ensure the correct parameter name is used and set the default value of the input appropriately. Rails provides helpers tailored to this task. These helpers lack the _tag suffix, for example +text_field+, +text_area+. @@ -207,7 +207,7 @@ WARNING: You must pass the name of an instance variable, i.e. +:person+ or +"per Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the "Active Record Validations and Callbacks":./activerecord_validations_callbacks.html#_using_the_tt_errors_tt_collection_in_your_view_templates guide. -h4. Binding a form to an object +h4. Binding a Form to an Object While this is an increase in comfort it is far from perfect. If Person has many attributes to edit then we would be repeating the name of the edited object many times. What we want to do is somehow bind a form to a model object, which is exactly what +form_for+ does. @@ -276,7 +276,7 @@ which produces the following output: The object yielded by +fields_for+ is a form builder like the one yielded by +form_for+ (in fact +form_for+ calls +fields_for+ internally). -h4. Relying on record identification +h4. Relying on Record Identification The Article model is directly available to users of the application, so -- following the best practices for developing with Rails -- you should declare it *a resource*. @@ -302,7 +302,7 @@ Rails will also automatically set the +class+ and +id+ of the form appropriately WARNING: When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify the model name, +:url+, and +:method+ explicitly. -h5. Dealing with namespaces +h5. Dealing with Namespaces If you have created namespaced routes, +form_for+ has a nifty shorthand for that too. If your application has an admin namespace then @@ -343,7 +343,7 @@ output: 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). -h3. Making select boxes with ease +h3. Making Select Boxes with Ease Select boxes in HTML require a significant amount of markup (one +OPTION+ element for each option to choose from), therefore it makes the most sense for them to be dynamically generated. @@ -360,7 +360,7 @@ Here is what the markup might look like: Here you have a list of cities whose names are presented to the user. Internally the application only wants to handle their IDs so they are used as the options' value attribute. Let's see how Rails can help out here. -h4. The select and options tag +h4. The Select and Option Tags The most generic helper is +select_tag+, which -- as the name implies -- simply generates the +SELECT+ tag that encapsulates an options string: @@ -404,7 +404,7 @@ Whenever Rails sees that the internal value of an option being generated matches TIP: The second argument to +options_for_select+ must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to +options_for_select+ -- you must pass 2. Be aware of values extracted from the +params+ hash as they are all strings. -h4. Select boxes for dealing with models +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+: @@ -429,7 +429,7 @@ As with other helpers, if you were to use the +select+ helper on a form builder 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
 ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) 
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 +h4. Option Tags from a Collection of Arbitrary Objects Generating options tags with +options_for_select+ requires that you create an array containing the text and value for each option. But what if you had a City model (perhaps an Active Record one) and you wanted to generate option tags from a collection of those objects? One solution would be to make a nested array by iterating over them: @@ -454,7 +454,7 @@ To recap, +options_from_collection_for_select+ is to +collection_select+ what +o NOTE: Pairs passed to +options_for_select+ should have the name first and the id second, however with +options_from_collection_for_select+ the first argument is the value method and the second the text method. -h4. Time zone and country select +h4. Time Zone and Country Select To leverage time zone support in Rails, you have to ask your users what time zone they are in. Doing so would require generating select options from a list of pre-defined TimeZone objects using +collection_select+, but you can simply use the +time_zone_select+ helper that already wraps this: @@ -475,7 +475,7 @@ The date and time helpers differ from all the other form helpers in two importan Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc.). -h4. Barebones helpers +h4. Barebones Helpers The +select_*+ family of helpers take as their first argument an instance of Date, Time or DateTime that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example @@ -499,7 +499,7 @@ Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, pa The +:prefix+ option is the key used to retrieve the hash of date components from the +params+ hash. Here it was set to +start_date+, if omitted it will default to +date+. -h4. Model object helpers +h4. Model Object Helpers +select_date+ does not work well with forms that update or create Active Record objects as Active Record expects each element of the +params+ hash to correspond to one attribute. The model object helpers for dates and times submit parameters with special names, when Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example: @@ -524,7 +524,7 @@ which results in a +params+ hash like When this is passed to +Person.new+ (or +update_attributes+), Active Record spots that these parameters should all be used to construct the +birth_date+ attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as +Date.civil+. -h4. Common options +h4. Common Options Both families of helpers use the same core set of functions to generate the individual select tags and so both accept largely the same options. In particular, by default Rails will generate year options 5 years either side of the current year. If this is not an appropriate range, the +:start_year+ and +:end_year+ options override this. For an exhaustive list of the available options, refer to the "API documentation":http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html. @@ -532,7 +532,7 @@ As a rule of thumb you should be using +date_select+ when working with model obj NOTE: In many cases the built-in date pickers are clumsy as they do not aid the user in working out the relationship between the date and the day of the week. -h4. Individual components +h4. Individual Components Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component +select_year+, +select_month+, +select_day+, +select_hour+, +select_minute+, +select_second+. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for +select_year+, "month" for +select_month+ etc.) although this can be overriden with the +:field_name+ option. The +:prefix+ option works in the same way that it does for +select_date+ and +select_time+ and has the same default value. @@ -563,7 +563,7 @@ The following two forms both upload a file. Rails provides the usual pair of helpers: the barebones +file_field_tag+ and the model oriented +file_field+. The only difference with other helpers is that you cannot set a default value for file inputs as this would have no meaning. As you would expect in the first case the uploaded file is in +params[:picture]+ and in the second case in +params[:person][:picture]+. -h4. What gets uploaded +h4. What Gets Uploaded The object in the +params+ hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an +original_filename+ attribute containing the name the file had on the user's computer and a +content_type+ attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in +#{Rails.root}/public/uploads+ under the same name as the original file (assuming the form was the one in the previous example). @@ -605,9 +605,9 @@ can be replaced with by defining a LabellingFormBuilder class similar to the following: -class LabellingFormBuilder < FormBuilder +class LabellingFormBuilder < ActionView::Helpers::FormBuilder def text_field(attribute, options={}) - label(attribute) + text_field(attribute, options) + label(attribute) + super end end @@ -631,7 +631,7 @@ Fundamentally HTML forms don't know about any sort of structured data, all they 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
 ActionController::UrlEncodedPairParser.parse_query_parameters "name=fred&phone=0123456789" # => {"name"=>"fred", "phone"=>"0123456789"} 
-h4. Basic structures +h4. Basic Structures The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in +params+. For example if a form contains @@ -669,7 +669,7 @@ Normally Rails ignores duplicate parameter names. If the parameter name contains This would result in +params[:person][:phone_number]+ being an array. -h4. Combining them +h4. Combining Them We can mix and match these two concepts. For example, one element of a hash might be an array as in the previous example, or you can have an array of hashes. For example a form might let you create any number of addresses by repeating the following form fragment @@ -685,7 +685,7 @@ There's a restriction, however, while hashes can be nested arbitrarily, only one WARNING: Array parameters do not play well with the +check_box+ helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. The +check_box+ helper fakes this by creating a second hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted and if it is checked then both are submitted but the value submitted by the checkbox takes precedence. When working with array parameters this duplicate submission will confuse Rails since duplicate input names are how it decides when to start a new array element. It is preferable to either use +check_box_tag+ or to use hashes instead of arrays. -h4. Using form helpers +h4. Using Form Helpers The previous sections did not use the Rails form helpers at all. While you can craft the input names yourself and pass them directly to helpers such as +text_field_tag+ Rails also provides higher level support. The two tools at your disposal here are the name parameter to +form_for+ and +fields_for+ and the +:index+ option that helpers take. @@ -746,7 +746,7 @@ As a shortcut you can append [] to the name and omit the +:index+ option. This i produces exactly the same output as the previous example. -h3. Building Complex forms +h3. Building Complex Forms Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. While this guide has shown you all the pieces necessary to handle this, Rails does not yet have a standard end-to-end way of accomplishing this, but many have come up with viable approaches. These include: diff --git a/vendor/rails/railties/guides/source/getting_started.textile b/vendor/rails/railties/guides/source/getting_started.textile index 3d6c16f1..97f141b5 100644 --- a/vendor/rails/railties/guides/source/getting_started.textile +++ b/vendor/rails/railties/guides/source/getting_started.textile @@ -1,4 +1,4 @@ -h2. Getting Started With Rails +h2. Getting Started with Rails This guide covers getting up and running with Ruby on Rails. After reading it, you should be familiar with: @@ -23,7 +23,7 @@ It is highly recommended that you *familiarize yourself with Ruby before diving * "Mr. Neighborly’s Humble Little Ruby Book":http://www.humblelittlerubybook.com * "Programming Ruby":http://www.rubycentral.com/book -* "Why’s (Poignant) Guide to Ruby":http://poignantguide.net/ruby +* "Why’s (Poignant) Guide to Ruby":http://poignantguide.net/ruby/ h3. What is Rails? @@ -115,6 +115,7 @@ If you’d like more details on REST as an architectural style, these resources * "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 @@ -174,7 +175,7 @@ In any case, Rails will create a folder in your working directory called blo |log/|Application log files.| |public/|The only folder seen to the world as-is. This is where your images, javascript, stylesheets (CSS), and other static files go.| |script/|Scripts provided by Rails to do recurring tasks, such as benchmarking, plugin installation, and starting the console or the web server.| -|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing_rails_applications.html| +|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html| |tmp/|Temporary files| |vendor/|A place for 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.| @@ -309,7 +310,7 @@ This line illustrates one tiny bit of the "convention over configuration" approa Now if you navigate to +http://localhost:3000+ in your browser, you'll see the +home/index+ view. -NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing_outside_in.html. +NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing.html. h3. Getting Up and Running Quickly With Scaffolding @@ -471,7 +472,7 @@ end This code sets the +@posts+ instance variable to an array of all posts in the database. +Post.find(:all)+ or +Post.all+ calls the +Post+ model to return all of the posts that are currently in the database, with no limiting conditions. -TIP: For more information on finding records with Active Record, see "Active Record Finders":finders.html. +TIP: For more information on finding records with Active Record, see "Active Record Query Interface":active_record_querying.html. The +respond_to+ block handles both HTML and XML calls to this action. If you browse to +http://localhost:3000/posts.xml+, you'll see all of the posts in XML format. The HTML format looks for a view in +app/views/posts/+ with a name that corresponds to the action name. Rails makes all of the instance variables from the action available to the view. Here's +app/view/posts/index.html.erb+: @@ -845,7 +846,7 @@ end Rails runs _before filters_ before any action in the controller. You can use the +:only+ clause to limit a before filter to only certain actions, or an +:except+ clause to specifically skip a before filter for certain actions. Rails also allows you to define _after filters_ that run after processing an action, as well as _around filters_ that surround the processing of actions. Filters can also be defined in external classes to make it easy to share them between controllers. -For more information on filters, see the "Action Controller Basics":actioncontroller_basics.html guide. +For more information on filters, see the "Action Controller Overview":action_controller_overview.html guide. h3. Adding a Second Model diff --git a/vendor/rails/railties/guides/source/i18n.textile b/vendor/rails/railties/guides/source/i18n.textile index bb445c0b..103ccb1c 100644 --- a/vendor/rails/railties/guides/source/i18n.textile +++ b/vendor/rails/railties/guides/source/i18n.textile @@ -12,37 +12,37 @@ So, in the process of _internationalizing_ your Rails application you have to: In the process of _localizing_ your application you'll probably want to do following three things: -* Replace or supplement Rails' default locale -- eg. date and time formats, month names, ActiveRecord model names, etc -* Abstract texts in your application into keyed dictionaries -- eg. flash messages, static texts in your views, etc +* Replace or supplement Rails' default locale -- e.g. date and time formats, month names, Active Record model names, etc +* Abstract strings in your application into keyed dictionaries -- e.g. flash messages, static text in your views, etc. * Store the resulting dictionaries somewhere This guide will walk you through the I18n API and contains a tutorial how to internationalize a Rails application from the start. endprologue. -NOTE: The Ruby I18n framework provides you with all neccessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See Rails "I18n Wiki":http://rails-i18n.org/wiki for more information. +NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See the Rails "I18n Wiki":http://rails-i18n.org/wiki for more information. -h3. How I18n in Ruby on Rails works +h3. How I18n in Ruby on Rails Works -Internationalization is a complex problem. Natural languages differ in so many ways (eg. in pluralization rules) that it is hard to provide tools for solving all problems at once. For that reason the Rails I18n API focuses on: +Internationalization is a complex problem. Natural languages differ in so many ways (e.g. in pluralization rules) that it is hard to provide tools for solving all problems at once. For that reason the Rails I18n API focuses on: * providing support for English and similar languages out of the box * making it easy to customize and extend everything for other languages -As part of this solution, *every static string in the Rails framework* -- eg. Active Record validation messages, time and date formats -- *has been internationalized*, so _localization_ of a Rails application means "over-riding" these defaults. +As part of this solution, *every static string in the Rails framework* -- e.g. Active Record validation messages, time and date formats -- *has been internationalized*, so _localization_ of a Rails application means "over-riding" these defaults. -h4. The overall architecture of the library +h4. The Overall Architecture of the Library Thus, the Ruby I18n gem is split into two parts: -* The public API of the i18n framework -- a Ruby module with public methods and definitions how the library works +* The public API of the i18n framework -- a Ruby module with public methods that define how the library works * A default backend (which is intentionally named _Simple_ backend) that implements these methods As a user you should always only access the public methods on the I18n module, but it is useful to know about the capabilities of the backend. -NOTE: It is possible (or even desirable) to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section "Using different backends":#usingdifferentbackends below. +NOTE: It is possible (or even desirable) to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section "Using different backends":#using-different-backends below. -h4. The public I18n API +h4. The Public I18n API The most important methods of the I18n API are: @@ -70,34 +70,34 @@ backend # Use a different backend So, let's internationalize a simple Rails application from the ground up in the next chapters! -h3. Setup the Rails application for internationalization +h3. Setup the Rails Application for Internationalization -There are just a few, simple steps to get up and running with I18n support for your application. +There are just a few simple steps to get up and running with I18n support for your application. -h4. Configure the I18n module +h4. Configure the I18n Module -Following the _convention over configuration_ philosophy, Rails will set-up your application with reasonable defaults. If you need different settings, you can overwrite them easily. +Following the _convention over configuration_ philosophy, Rails will set up your application with reasonable defaults. If you need different settings, you can overwrite them easily. -Rails adds all +.rb+ and +.yml+ files from +config/locales+ directory to your *translations load path*, automatically. +Rails adds all +.rb+ and +.yml+ files from the +config/locales+ directory to your *translations load path*, automatically. -See the default +en.yml+ locale in this directory, containing a sample pair of translation strings: +The default +en.yml+ locale in this directory contains a sample pair of translation strings: en: hello: "Hello world" -This means, that in the +:en+ locale, the key _hello_ will map to _Hello world_ string. Every string inside Rails is internationalized in this way, see for instance Active Record validation messages in the "+activerecord/lib/active_record/locale/en.yml+":http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml file or time and date formats in the "+activesupport/lib/active_support/locale/en.yml+":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend. +This means, that in the +:en+ locale, the key _hello_ will map to the _Hello world_ string. Every string inside Rails is internationalized in this way, see for instance Active Record validation messages in the "+activerecord/lib/active_record/locale/en.yml+":http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml file or time and date formats in the "+activesupport/lib/active_support/locale/en.yml+":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend. -The I18n library will use *English* as a *default locale*, ie. if you don't set a different locale, +:en+ will be used for looking up translations. +The I18n library will use *English* as a *default locale*, i.e. if you don't set a different locale, +:en+ will be used for looking up translations. -NOTE: The i18n library takes *pragmatic approach* to locale keys (after "some discussion":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like +:en+, +:pl+, not the _region_ part, like +:en-US+ or +:en-UK+, which are traditionally used for separating "languages" and "regional setting" or "dialects". (For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have £. Also, insults would be different in American and British English :) Reason for this pragmatic approach is that most of the time, you usually care about making your application available in different "languages", and working with locales is much simpler this way. However, nothing stops you from separating regional and other settings in the traditional way. In this case, you could eg. inherit from the default +en+ locale and then provide UK specific settings in a +:en-UK+ dictionary. +NOTE: The i18n library takes a *pragmatic approach* to locale keys (after "some discussion":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like +:en+, +:pl+, not the _region_ part, like +:en-US+ or +:en-UK+, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as +:cz+, +:th+ or +:es+ (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a +:en-UK+ dictionary. Various "Rails I18n plugins":http://rails-i18n.org/wiki such as "Globalize2":http://github.com/joshmh/globalize2/tree/master may help you implement it. The *translations load path* (+I18n.load_path+) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you. NOTE: The backend will lazy-load these translations when a translation is looked up for the first time. This makes it possible to just swap the backend with something else even after translations have already been announced. -The default +environment.rb+ files has instruction how to add locales from another directory and how to set different default locale. Just uncomment and edit the specific lines. +The default +environment.rb+ files has instruction how to add locales from another directory and how to set a different default locale. Just uncomment and edit the specific lines. # The internationalization framework can be changed @@ -107,31 +107,32 @@ The default +environment.rb+ files has instruction how to add locales from anoth # config.i18n.default_locale = :de -h4. Optional: custom I18n configuration setup +h4. Optional: Custom I18n Configuration Setup For the sake of completeness, let's mention that if you do not want to use the +environment.rb+ file for some reason, you can always wire up things manually, too. -To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an *initializer*: +To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an initializer: # in config/initializer/locale.rb # tell the I18n library where to find your translations -I18n.load_path << Dir[ File.join(RAILS_ROOT, 'lib', 'locale', '*.{rb,yml}') ] +I18n.load_path << Dir[ File.join(RAILS_ROOT, 'lib', 'locale', + '*.{rb,yml}') ] -# set default locale to something else then :en +# set default locale to something other than :en I18n.default_locale = :pt -h4. Setting and passing the locale +h4. Setting and Passing the Locale If you want to translate your Rails application to a *single language other than English* (the default locale), you can set I18n.default_locale to your locale in +environment.rb+ or an initializer as shown above, and it will persist through the requests. 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 choosed locale in a _session_ or a _cookie_. *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 "_RESTful_":http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about 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 _cookie_. *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 "_RESTful_":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. -The _setting part_ is easy. You can set locale in a +before_filter+ in the ApplicationController like this: +The _setting part_ is easy. You can set the locale in a +before_filter+ in the ApplicationController like this: before_filter :set_locale @@ -141,13 +142,13 @@ def set_locale end -This requires you to pass the locale as a URL query parameter as in +http://example.com/books?locale=pt+. (This is eg. Google's approach). So +http://localhost:3000?locale=pt+ will load the Portugese localization, whereas +http://localhost:3000?locale=de+ would load the German localization, and so on. You may skip the next section and head over to the *Internationalize your application* section, if you want to try things out by manually placing locale in the URL and reloading the page. +This requires you to pass the locale as a URL query parameter as in +http://example.com/books?locale=pt+. (This is, for example, Google's approach.) So +http://localhost:3000?locale=pt+ will load the Portugese localization, whereas +http://localhost:3000?locale=de+ would load the German localization, and so on. You may skip the next section and head over to the *Internationalize your application* section, if you want to try things out by manually placing the locale in the URL and reloading the page. -Of course, you probably don't want to manually include locale in every URL all over your application, or want the URLs look differently, eg. the usual +http://example.com/pt/books+ versus +http://example.com/en/books+. Let's discuss the different options you have. +Of course, you probably don't want to manually include the locale in every URL all over your application, or want the URLs look differently, e.g. the usual +http://example.com/pt/books+ versus +http://example.com/en/books+. Let's discuss the different options you have. -IMPORTANT: Following examples rely on having locales loaded into your application available as an array of strings like +["en", "es", "gr"]+. This is not inclued in current version of Rails 2.2 -- forthcoming Rails version 2.3 will contain easy accesor +available_locales+. (See "this commit":http://github.com/svenfuchs/i18n/commit/411f8fe7 and background at "Rails I18n Wiki":http://rails-i18n.org/wiki/pages/i18n-available_locales.) +IMPORTANT: The following examples rely on having available locales loaded into your application as an array of strings like +["en", "es", "gr"]+. This is not included in the current version of Rails 2.2 -- the forthcoming Rails version 2.3 will contain the easy accessor +available_locales+. (See "this commit":http://github.com/svenfuchs/i18n/commit/411f8fe7c8f3f89e9b6b921fa62ed66cb92f3af4 and background at "Rails I18n Wiki":http://rails-i18n.org/wiki/pages/i18n-available_locales.) -So, for having available locales easily available in Rails 2.2, we have to include this support manually in an initializer, like this: +So, for having available locales easily accessible in Rails 2.2, we have to include this support manually in an initializer, like this: # config/initializers/available_locales.rb @@ -180,11 +181,11 @@ class ApplicationController < ActionController::Base end -h4. Setting locale from the domain name +h4. Setting the Locale from the Domain Name -One option you have is to set the locale from the domain name where your application runs. For example, we want +www.example.com+ to load English (or default) locale, and +www.example.es+ to load Spanish locale. Thus the _top-level domain name_ is used for locale setting. This has several advantages: +One option you have is to set the locale from the domain name where your application runs. For example, we want +www.example.com+ to load the English (or default) locale, and +www.example.es+ to load the Spanish locale. Thus the _top-level domain name_ is used for locale setting. This has several advantages: -* Locale is an _obvious_ part of the URL +* The locale is an _obvious_ part of the URL * People intuitively grasp in which language the content will be displayed * It is very trivial to implement in Rails * Search engines seem to like that content in different languages lives at different, inter-linked domains @@ -208,7 +209,7 @@ def extract_locale_from_tld end -We can also set the locale from the _subdomain_ in very similar way: +We can also set the locale from the _subdomain_ in a very similar way: # Get locale code from request subdomain (like http://it.application.local:3000) @@ -231,15 +232,15 @@ assuming you would set +APP_CONFIG[:deutsch_website_url]+ to some value like +ht This solution has aforementioned advantages, however, you may not be able or may not want to provide different localizations ("language versions") on different domains. The most obvious solution would be to include locale code in the URL params (or request path). -h4. Setting locale from the URL params +h4. Setting the Locale from the URL Params -Most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the +I18n.locale = params[:locale]+ _before_filter_ in the first example. We would like to have URLs like +www.example.com/books?locale=ja+ or +www.example.com/ja/books+ in this case. +The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the +I18n.locale = params[:locale]+ _before_filter_ in the first example. We would like to have URLs like +www.example.com/books?locale=ja+ or +www.example.com/ja/books+ in this case. -This approach has almost the same set of advantages as setting the locale from domain name: namely that it's RESTful and in accord with rest of the World Wide Web. It does require a little bit more work to implement, though. +This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the rest of the World Wide Web. It does require a little bit more work to implement, though. -Getting the locale from +params+ and setting it accordingly is not hard; including it in every URL and thus *passing it through the requests* is. To include an explicit option in every URL (eg. +link_to( books_url(:locale => I18n.locale) )+) would be tedious and probably impossible, of course. +Getting the locale from +params+ and setting it accordingly is not hard; including it in every URL and thus *passing it through the requests* is. To include an explicit option in every URL (e.g. +link_to( books_url(:locale => I18n.locale))+) would be tedious and probably impossible, of course. -Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its "+*ApplicationController#default_url_options*+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000515, which is useful precisely in this scenario: it enables us to set "defaults" for "+url_for+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000503 and helper methods dependent on it (by implementing/overriding this method). +Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its "+ApplicationController#default_url_options+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000515, which is useful precisely in this scenario: it enables us to set "defaults" for "+url_for+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000503 and helper methods dependent on it (by implementing/overriding this method). We can include something like this in our ApplicationController then: @@ -251,20 +252,20 @@ def default_url_options(options={}) end -Every helper method dependent on +url_for+ (eg. helpers for named routes like +root_path+ or +root_url+, resource routes like +books_path+ or +books_url+, etc.) will now *automatically include the locale in the query string*, like this: +http://localhost:3001/?locale=ja+. +Every helper method dependent on +url_for+ (e.g. helpers for named routes like +root_path+ or +root_url+, resource routes like +books_path+ or +books_url+, etc.) will now *automatically include the locale in the query string*, like this: +http://localhost:3001/?locale=ja+. -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 application domain: and URLs should reflect this. +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 look like this: +www.example.com/en/books+ (which loads English locale) and +www.example.com/nl/books+ (which loads 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 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: # config/routes.rb map.resources :books, :path_prefix => '/:locale' -Now, when you call +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 Netherlands locale, then, and following calls to +books_path+ should return +"/nl/books"+ (because the locale changed). -Of course, you need to take special care of root URL (usually "homepage" or "dashboard") of your application. An URL like +http://localhost:3001/nl+ will not work automatically, because the +map.root :controller => "dashboard"+ declaration in your +routes.rb+ doesn't take locale into account. (And rightly so. There's only one "root" URL.) +Of course, you need to take special care of the root URL (usually "homepage" or "dashboard") of your application. An URL like +http://localhost:3001/nl+ will not work automatically, because the +map.root :controller => "dashboard"+ declaration in your +routes.rb+ doesn't take locale into account. (And rightly so: there's only one "root" URL.) You would probably need to map URLs like these: @@ -275,18 +276,18 @@ map.dashboard '/:locale', :controller => "dashboard" Do take special care about the *order of your routes*, so this route declaration does not "eat" other ones. (You may want to add it directly before the +map.root+ declaration.) -IMPORTANT: This solution has currently one rather big *downside*. Due to the _default_url_options_ implementation, you have to pass the +:id+ option explicitely, like this: +link_to 'Show', book_url(:id => book)+ and not depend on Rails' magic in code like +link_to 'Show', book+. If this should be a problem, have a look on two plugins which simplify working with routes in this way: Sven Fuchs's "_routing_filter_":http://github.com/svenfuchs/routing-filter/tree/master and Raul Murciano's "_translate_routes_":http://github.com/raul/translate_routes/tree/master. See also the page "How to encode the current locale in the URL":http://rails-i18n.org/wiki/pages/how-to-encode-the-current-locale-in-the-url in the Rails i18n Wiki. +IMPORTANT: This solution has currently one rather big *downside*. Due to the _default_url_options_ implementation, you have to pass the +:id+ option explicitely, like this: +link_to 'Show', book_url(:id => book)+ and not depend on Rails' magic in code like +link_to 'Show', book+. If this should be a problem, have a look at two plugins which simplify work with routes in this way: Sven Fuchs's "routing_filter":http://github.com/svenfuchs/routing-filter/tree/master and Raul Murciano's "translate_routes":http://github.com/raul/translate_routes/tree/master. See also the page "How to encode the current locale in the URL":http://rails-i18n.org/wiki/pages/how-to-encode-the-current-locale-in-the-url in the Rails i18n Wiki. -h4. Setting locale from the client supplied information +h4. Setting the Locale from the Client Supplied Information -In specific cases, it would make sense to set locale from client supplied information, ie. not from URL. This information may come for example from users' preffered language (set in their browser), can be based on users' geographical location inferred from their IP, or users can provide it simply by choosing locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites -- see the box about _sessions_, _cookies_ and RESTful architecture above. +In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' prefered language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites -- see the box about _sessions_, _cookies_ and RESTful architecture above. -h5. Using Accept-Language +h5. Using +Accept-Language+ One source of client supplied information would be an +Accept-Language+ HTTP header. People may "set this in their browser":http://www.w3.org/International/questions/qa-lang-priorities or other clients (such as _curl_). -A trivial implementation of using +Accept-Language+ header would be: +A trivial implementation of using an +Accept-Language+ header would be: def set_locale @@ -300,21 +301,21 @@ def extract_locale_from_accept_language_header end -Of course, in production environment you would need much robust code, and could use a plugin such as Iaian Hecker's "http_accept_language":http://github.com/iain/http_accept_language or even Rack middleware such as Ryan Tomayko's "locale":http://github.com/rtomayko/rack-contrib/blob/master/lib/rack/locale.rb. +Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's "http_accept_language":http://github.com/iain/http_accept_language/tree/master or even Rack middleware such as Ryan Tomayko's "locale":http://github.com/rtomayko/rack-contrib/blob/master/lib/rack/locale.rb. -h5. Using GeoIP (or similar) database +h5. Using GeoIP (or Similar) Database -Another way of choosing the locale from client's information would be to use a database for mapping client IP to region, such as "GeoIP Lite Country":http://www.maxmind.com/app/geolitecountry. The mechanics of the code would be very similar to the code above -- you would need to query database for user's IP, and lookup your preffered locale for the country/region/city returned. +Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as "GeoIP Lite Country":http://www.maxmind.com/app/geolitecountry. The mechanics of the code would be very similar to the code above -- you would need to query the database for the user's IP, and look up your prefered locale for the country/region/city returned. -h5. User profile +h5. User Profile -You can also provide users of your application with means to set (and possibly over-ride) locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above -- you'd probably let users choose a locale from a dropdown list and save it to their profile in database. Then you'd set the locale to this value. +You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above -- you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value. -h3. Internationalizing your application +h3. Internationalizing your Application -OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale should be used and how to preserve it between requests. With that in place, you're now ready for the really interesting stuff. +OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests. With that in place, you're now ready for the really interesting stuff. -Let's _internationalize_ our application, ie. abstract every locale-specific parts, and that _localize_ it, ie. provide neccessary translations for these abstracts. +Let's _internationalize_ our application, i.e. abstract every locale-specific parts, and then _localize_ it, i.e. provide neccessary translations for these abstracts. You most probably have something like this in one of your applications: @@ -359,7 +360,7 @@ When you now render this view, it will show an error message which tells you tha !images/i18n/demo_translation_missing.png(rails i18n demo translation missing)! -NOTE: Rails adds a +t+ (+translate+) helper method to your views so that you do not need to spell out +I18n.t+ all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a ++. +NOTE: Rails adds a +t+ (+translate+) helper method to your views so that you do not need to spell out +I18n.t+ all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a +<span class="translation_missing">+. So let's add the missing translations into the dictionary files (i.e. do the "localization" part): @@ -385,9 +386,9 @@ And when you change the URL to pass the pirate locale (+http://localhost:3000?lo NOTE: You need to restart the server when you add new locale files. -You may use YAML (+.yml+) or plain Ruby (+.rb+) files for storing your translations in SimpleStore. YAML is the preffered option among Rails developers, has one big disadvantage, though. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into Ruby file.) +You may use YAML (+.yml+) or plain Ruby (+.rb+) files for storing your translations in SimpleStore. YAML is the prefered option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.) -h4. Adding Date/Time formats +h4. Adding Date/Time Formats OK! Now let's add a timestamp to the view, so we can demo the *date/time localization* feature as well. To localize the time format you pass the Time object to +I18n.l+ or (preferably) use Rails' +#l+ helper. You can pick a format by passing the +:format+ option -- by default the +:default+ format is used. @@ -412,11 +413,17 @@ So that would give you: !images/i18n/demo_localized_pirate.png(rails i18n demo localized time to pirate)! -TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected. Of course, there's a great chance that somebody already did all the work by *translating Rails's defaults for your locale*. See the "rails-i18n repository at Github":http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale for an archive of various locale files. When you put such file(s) in +config/locale/+ directory, they will automatically ready for use. +TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected (at least for the 'pirate' locale). Of course, there's a great chance that somebody already did all the work by *translating Rails's defaults for your locale*. See the "rails-i18n repository at Github":http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale for an archive of various locale files. When you put such file(s) in +config/locale/+ directory, they will automatically be ready for use. -h4. Organization of locale files +h4. Localized Views -When you are using the default SimpleStore, shipped with the i18n library, you store dictionaries in plain-text files on the disc. Putting translations for all parts of your application in one file per locale could be hard to manage. You can store these files in a hierarchy which makes sense to you. +Rails 2.3 introduces another convenient localization feature: localized views (templates). Let's say you have a _BooksController_ in your application. Your _index_ action renders content in +app/views/books/index.html.erb+ template. When you put a _localized variant_ of this template: *+index.es.html.erb+* in the same directory, Rails will render content in this template, when the locale is set to +:es+. When the locale is set to the default locale, the generic +index.html.erb+ view will be used. (Future Rails versions may well bring this _automagic_ localization to assets in +public+, etc.) + +You can make use of this feature, e.g. when working with a large amount of static content, which would be clumsy to put inside YAML or Ruby dictionaries. Bear in mind, though, that any change you would like to do later to the template must be propagated to all of them. + +h4. Organization of Locale Files + +When you are using the default SimpleStore shipped with the i18n library, dictionaries are stored in plain-text files on the disc. Putting translations for all parts of your application in one file per locale could be hard to manage. You can store these files in a hierarchy which makes sense to you. For example, your +config/locale+ directory could look like this: @@ -443,13 +450,18 @@ For example, your +config/locale+ directory could look like this: |-----en.rb -This way, you can separate model and model attribute names from text inside views, and all of this from the "defaults" (eg. date and time formats). +This way, you can separate model and model attribute names from text inside views, and all of this from the "defaults" (e.g. date and time formats). Other stores for the i18n library could provide different means of such separation. -Other stores for the i18n library could provide different means of such separation. +NOTE: The default locale loading mechanism in Rails does not load locale files in nested dictionaries, like we have here. So, for this to work, we must explicitly tell Rails to look further: + + + # config/environment.rb + config.i18n.load_path += Dir[File.join(RAILS_ROOT, 'config', 'locales', '**', '*.{rb,yml}')] + Do check the "Rails i18n Wiki":http://rails-i18n.org/wiki for list of tools available for managing translations. -h3. Overview of the I18n API features +h3. Overview of the I18n API Features You should have good understanding of using the i18n library now, knowing all neccessary aspects of internationalizing a basic Rails application. In the following chapters, we'll cover it's features in more depth. @@ -458,11 +470,11 @@ Covered are features like these: * looking up translations * interpolating data into translations * pluralizing translations -* localizing dates, numbers, currency etc. +* localizing dates, numbers, currency, etc. -h4. Looking up translations +h4. Looking up Translations -h5. Basic lookup, scopes and nested keys +h5. Basic Lookup, Scopes and Nested Keys Translations are looked up by keys which can be both Symbols or Strings, so these calls are equivalent: @@ -471,63 +483,80 @@ I18n.t :message I18n.t 'message' -+translate+ also takes a +:scope+ option which can contain one or many additional keys that will be used to specify a “namespace” or scope for a translation key: +The +translate+ method also takes a +:scope+ option which can contain one or more additional keys that will be used to specify a “namespace” or scope for a translation key: -I18n.t :invalid, :scope => [:active_record, :error_messages] +I18n.t :invalid, :scope => [:activerecord, :errors, :messages] This looks up the +:invalid+ message in the Active Record error messages. -Additionally, both the key and scopes can be specified as dot separated keys as in: +Additionally, both the key and scopes can be specified as dot-separated keys as in: -I18n.translate :"active_record.error_messages.invalid" +I18n.translate :"activerecord.errors.messages.invalid" Thus the following calls are equivalent: -I18n.t 'active_record.error_messages.invalid' -I18n.t 'error_messages.invalid', :scope => :active_record -I18n.t :invalid, :scope => 'active_record.error_messages' -I18n.t :invalid, :scope => [:active_record, :error_messages] +I18n.t 'activerecord.errors.messages.invalid' +I18n.t 'errors.messages.invalid', :scope => :active_record +I18n.t :invalid, :scope => 'activerecord.errors.messages' +I18n.t :invalid, :scope => [:activerecord, :errors, :messages] h5. Defaults -When a default option is given its value will be returned if the translation is missing: +When a +:default+ option is given, its value will be returned if the translation is missing: I18n.t :missing, :default => 'Not here' # => 'Not here' -If the default value is a Symbol it will be used as a key and translated. One can provide multiple values as default. The first one that results in a value will be returned. +If the +:default+ value is a Symbol, it will be used as a key and translated. One can provide multiple values as default. The first one that results in a value will be returned. -E.g. the following first tries to translate the key +:missing+ and then the key +:also_missing.+ As both do not yield a result the string "Not here" will be returned: +E.g., the following first tries to translate the key +:missing+ and then the key +:also_missing.+ As both do not yield a result, the string "Not here" will be returned: I18n.t :missing, :default => [:also_missing, 'Not here'] # => 'Not here' -h5. Bulk and namespace lookup +h5. Bulk and Namespace Lookup -To lookup multiple translations at once an array of keys can be passed: +To look up multiple translations at once, an array of keys can be passed: -I18n.t [:odd, :even], :scope => 'active_record.error_messages' +I18n.t [:odd, :even], :scope => 'activerecord.errors.messages' # => ["must be odd", "must be even"] -Also, a key can translate to a (potentially nested) hash as grouped translations. E.g. one can receive all Active Record error messages as a Hash with: +Also, a key can translate to a (potentially nested) hash of grouped translations. E.g., one can receive _all_ Active Record error messages as a Hash with: -I18n.t 'active_record.error_messages' +I18n.t 'activerecord.errors.messages' # => { :inclusion => "is not included in the list", :exclusion => ... } +h5. "Lazy" Lookup + +Rails 2.3 implements a convenient way to look up the locale inside _views_. When you have the following dictionary: + + +es: + books: + index: + title: "Título" + + +you can look up the +books.index.title+ value *inside* +app/views/books/index.html.erb+ template like this (note the dot): + + +<%= t '.title' %> + + h4. Interpolation In many cases you want to abstract your translations so that *variables can be interpolated into the translation*. For this reason the I18n API provides an interpolation feature. @@ -540,12 +569,11 @@ I18n.translate :thanks, :name => 'Jeremy' # => 'Thanks Jeremy!' -If a translation uses +:default+ or +:scope+ as a interpolation variable an I+18n::ReservedInterpolationKey+ exception is raised. If a translation expects an interpolation variable but it has not been passed to +#translate+ an +I18n::MissingInterpolationArgument+ exception is raised. - +If a translation uses +:default+ or +:scope+ as an interpolation variable, an I+18n::ReservedInterpolationKey+ exception is raised. If a translation expects an interpolation variable, but this has not been passed to +#translate+, an +I18n::MissingInterpolationArgument+ exception is raised. h4. Pluralization -In English there's only a singular and a plural form for a given string, e.g. "1 message" and "2 messages". Other languages ("Arabic":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ar, "Japanese":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ja, "Russian":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ru and many more) have different grammars that have additional or less "plural forms":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html. Thus, the I18n API provides a flexible pluralization feature. +In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ("Arabic":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ar, "Japanese":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ja, "Russian":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ru and many more) have different grammars that have additional or fewer "plural forms":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html. Thus, the I18n API provides a flexible pluralization feature. The +:count+ interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR: @@ -566,13 +594,13 @@ entry[count == 1 ? 0 : 1] I.e. the translation denoted as +:one+ is regarded as singular, the other is used as plural (including the count being zero). -If the lookup for the key does not return an Hash suitable for pluralization an +18n::InvalidPluralizationData+ exception is raised. +If the lookup for the key does not return a Hash suitable for pluralization, an +18n::InvalidPluralizationData+ exception is raised. -h4. Setting and passing a locale +h4. Setting and Passing a Locale The locale can be either set pseudo-globally to +I18n.locale+ (which uses +Thread.current+ like, e.g., +Time.zone+) or can be passed as an option to +#translate+ and +#localize+. -If no locale is passed +I18n.locale+ is used: +If no locale is passed, +I18n.locale+ is used: I18n.locale = :de @@ -587,15 +615,15 @@ I18n.t :foo, :locale => :de I18n.l Time.now, :locale => :de -+I18n.locale+ defaults to +I18n.default_locale+ which defaults to :+en+. The default locale can be set like this: +The +I18n.locale+ defaults to +I18n.default_locale+ which defaults to :+en+. The default locale can be set like this: I18n.default_locale = :de -h3. How to store your custom translations +h3. How to Store your Custom Translations -The shipped Simple backend allows you to store translations in both plain Ruby and YAML format. [2] +The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format. [2] For example a Ruby Hash providing translations can look like this: @@ -617,9 +645,9 @@ pt: bar: baz -As you see in both cases the toplevel key is the locale. +:foo+ is a namespace key and +:bar+ is the key for the translation "baz". +As you see, in both cases the toplevel key is the locale. +:foo+ is a namespace key and +:bar+ is the key for the translation "baz". -Here is a "real" example from the ActiveSupport +en.yml+ translations YAML file: +Here is a "real" example from the Active Support +en.yml+ translations YAML file: en: @@ -639,11 +667,11 @@ I18n.t :short, :scope => 'date.formats' I18n.t :short, :scope => [:date, :formats] -Generally we recommend using YAML as a format for storing translations. There are cases though where you want to store Ruby lambdas as part of your locale data, e.g. for special date. +Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats. -h4. Translations for Active Record models +h4. Translations for Active Record Models -You can use the methods +Model.human_name+ and +Model.human_attribute_name(attribute)+ to transparently lookup translations for your model and attribute names. +You can use the methods +Model.human_name+ and +Model.human_attribute_name(attribute)+ to transparently look up translations for your model and attribute names. For example when you add the following translations: @@ -660,9 +688,9 @@ en: Then +User.human_name+ will return "Dude" and +User.human_attribute_name(:login)+ will return "Handle". -h5. Error message scopes +h5. Error Message Scopes -Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes and/or validations. It also transparently takes single table inheritance into account. +Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes, and/or validations. It also transparently takes single table inheritance into account. This gives you quite powerful means to flexibly adjust your messages to your application's needs. @@ -674,23 +702,23 @@ class User < ActiveRecord::Base end -The key for the error message in this case is +:blank+. Active Record will lookup this key in the namespaces: +The key for the error message in this case is +:blank+. Active Record will look up this key in the namespaces: -activerecord.errors.messages.models.[model_name].attributes.[attribute_name] -activerecord.errors.messages.models.[model_name] +activerecord.errors.models.[model_name].attributes.[attribute_name] +activerecord.errors.models.[model_name] activerecord.errors.messages Thus, in our example it will try the following keys in this order and return the first result: -activerecord.errors.messages.models.user.attributes.name.blank -activerecord.errors.messages.models.user.blank +activerecord.errors.models.user.attributes.name.blank +activerecord.errors.models.user.blank activerecord.errors.messages.blank -When your models are additionally using inheritance then the messages are looked up for the inherited model class names are looked up. +When your models are additionally using inheritance then the messages are looked up in the inheritance chain. For example, you might have an Admin model inheriting from User: @@ -710,9 +738,9 @@ activerecord.errors.models.user.blank activerecord.errors.messages.blank -This way you can provide special translations for various error messages at different points in your models inheritance chain and in the attributes, models or default scopes. +This way you can provide special translations for various error messages at different points in your models inheritance chain and in the attributes, models, or default scopes. -h5. Error message interpolation +h5. Error Message Interpolation The translated model name, translated attribute name, and value are always available for interpolation. @@ -743,9 +771,9 @@ So, for example, instead of the default error message +"can not be blank"+ you c | validates_numericality_of | :odd | :odd | -| | validates_numericality_of | :even | :even | -| -h5. Translations for the Active Record error_messages_for helper +h5. Translations for the Active Record +error_messages_for+ Helper -If you are using the Active Record +error_messages_for+ helper you will want to add translations for it. +If you are using the Active Record +error_messages_for+ helper, you will want to add translations for it. Rails ships with the following translations: @@ -760,44 +788,44 @@ en: body: "There were problems with the following fields:" -h4. Overview of other built-in methods that provide I18n support +h4. Overview of Other Built-In Methods that Provide I18n Support Rails uses fixed strings and other localizations, such as format strings and other format information in a couple of helpers. Here's a brief overview. -h5. ActionView helper methods +h5. Action View Helper Methods -* +distance_of_time_in_words+ translates and pluralizes its result and interpolates the number of seconds, minutes, hours and so on. See "datetime.distance_in_words":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L51 translations. +* +distance_of_time_in_words+ translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See "datetime.distance_in_words":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L51 translations. -* +datetime_select+ and +select_month+ use translated month names for populating the resulting select tag. See "date.month_names":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15 for translations. +datetime_select+ also looks up the order option from "date.order":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18 (unless you pass the option explicitely). All date select helpers translate the prompt using the translations in the "datetime.prompts":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L83 scope if applicable. +* +datetime_select+ and +select_month+ use translated month names for populating the resulting select tag. See "date.month_names":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15 for translations. +datetime_select+ also looks up the order option from "date.order":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18 (unless you pass the option explicitely). All date selection helpers translate the prompt using the translations in the "datetime.prompts":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L83 scope if applicable. -* The +number_to_currency+, +number_with_precision+, +number_to_percentage+, +number_with_delimiter+ and +humber_to_human_size+ helpers use the number format settings located in the "number":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2 scope. +* 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":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2 scope. -h5. Active Record methods +h5. Active Record Methods * +human_name+ and +human_attribute_name+ use translations for model names and attribute names if available in the "activerecord.models":http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L43 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 +human_name+ 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":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91 (and defaults to +' '+). +*+ 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":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91 (and which defaults to +' '+). -h5. ActiveSupport methods +h5. Active Support Methods * +Array#to_sentence+ uses format settings as given in the "support.array":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L30 scope. -h3. Customize your I18n setup +h3. Customize your I18n Setup -h4. Using different backends +h4. Using Different Backends -For several reasons the shipped Simple backend only does the "simplest thing that ever could work" _for Ruby on Rails_ [3] ... which means that it is only guaranteed to work for English and, as a side effect, languages that are very similar to English. Also, the simple backend is only capable of reading translations but can not dynamically store them to any format. +For several reasons the Simple backend shipped with Active Support only does the "simplest thing that could possibly work" _for Ruby on Rails_ [3] ... which means that it is only guaranteed to work for English and, as a side effect, languages that are very similar to English. Also, the simple backend is only capable of reading translations but can not dynamically store them to any format. -That does not mean you're stuck with these limitations though. The Ruby I18n gem makes it very easy to exchange the Simple backend implementation with something else that fits better for your needs. E.g. you could exchange it with Globalize's Static backend: +That does not mean you're stuck with these limitations, though. The Ruby I18n gem makes it very easy to exchange the Simple backend implementation with something else that fits better for your needs. E.g. you could exchange it with Globalize's Static backend: I18n.backend = Globalize::Backend::Static.new -h4. Using different exception handlers +h4. Using Different Exception Handlers The I18n API defines the following exceptions that will be raised by backends when the corresponding unexpected conditions occur: @@ -810,11 +838,11 @@ ReservedInterpolationKey # the translation contains a reserved interpolation UnknownFileType # the backend does not know how to handle a file type that was added to I18n.load_path -The I18n API will catch all of these exceptions when they were thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for +MissingTranslationData+ exceptions. When a +MissingTranslationData+ exception has been caught it will return the exception’s error message string containing the missing key/scope. +The I18n API will catch all of these exceptions when they are thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for +MissingTranslationData+ exceptions. When a +MissingTranslationData+ exception has been caught, it will return the exception’s error message string containing the missing key/scope. 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: module I18n @@ -828,9 +856,9 @@ I18n.exception_handler = :just_raise_that_exception This would re-raise all caught exceptions including +MissingTranslationData+. -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+. +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+. -To do so the helper forces +I18n#translate+ to raise exceptions no matter what exception handler is defined by setting the +:raise+ option: +To do so, the helper forces +I18n#translate+ to raise exceptions no matter what exception handler is defined by setting the +:raise+ option: I18n.t :foo, :raise => true # always re-raises exceptions from the backend @@ -838,16 +866,16 @@ I18n.t :foo, :raise => true # always re-raises exceptions from the backend h3. Conclusion -At this point you hopefully have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project. +At this point you should have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project. If you find anything missing or wrong in this guide please file a ticket on "our issue tracker":http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview. If you want to discuss certain portions or have questions please sign up to our "mailinglist":http://groups.google.com/group/rails-i18n. h3. Contributing to Rails I18n -I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first and then cherry-picking the best bread of most widely useful features second for inclusion to the core. +I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first, and only then cherry-picking the best-of-bread of most widely useful features for inclusion in the core. -Thus we encourage everybody to experiment with new ideas and features in plugins or other libraries and make them available to the community. (Don't forget to announce your work on our "mailinglist":http://groups.google.com/group/rails-i18n!) +Thus we encourage everybody to experiment with new ideas and features in plugins or other libraries and make them available to the community. (Don't forget to announce your work on our "mailing list":http://groups.google.com/group/rails-i18n!) If you find your own locale (language) missing from our "example translations data":http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale repository for Ruby on Rails, please "_fork_":http://github.com/guides/fork-a-project-and-submit-your-modifications the repository, add your data and send a "pull request":http://github.com/guides/pull-requests. @@ -876,7 +904,7 @@ fn1. Or, to quote "Wikipedia":http://en.wikipedia.org/wiki/Internationalization_ fn2. Other backends might allow or require to use other formats, e.g. a GetText backend might allow to read GetText files. -fn3. One of these reasons is that we don't want to any unnecessary load for applications that do not need any I18n capabilities, so we need to keep the I18n library as simple as possible for English. Another reason is that it is virtually impossible to implement a one-fits-all solution for all problems related to I18n for all existing languages. So a solution that allows us to exchange the entire implementation easily is appropriate anyway. This also makes it much easier to experiment with custom features and extensions. +fn3. One of these reasons is that we don't want to imply any unnecessary load for applications that do not need any I18n capabilities, so we need to keep the I18n library as simple as possible for English. Another reason is that it is virtually impossible to implement a one-fits-all solution for all problems related to I18n for all existing languages. So a solution that allows us to exchange the entire implementation easily is appropriate anyway. This also makes it much easier to experiment with custom features and extensions. h3. Changelog diff --git a/vendor/rails/railties/guides/source/index.erb.textile b/vendor/rails/railties/guides/source/index.erb.textile index 49d8cad4..4c8dd65a 100644 --- a/vendor/rails/railties/guides/source/index.erb.textile +++ b/vendor/rails/railties/guides/source/index.erb.textile @@ -32,7 +32,7 @@ h3. Models This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner. <% end %> -<% guide("Active Record Validations and Callbacks", 'activerecord_validations_callbacks.html', :ticket => 26) do %> +<% guide("Active Record Validations and Callbacks", 'activerecord_validations_callbacks.html') do %> This guide covers how you can use Active Record validations and callbacks. <% end %> @@ -40,7 +40,7 @@ h3. Models This guide covers all the associations provided by Active Record. <% end %> -<% guide("Active Record Query Interface", 'active_record_querying.html', :ticket => 16) do %> +<% guide("Active Record Query Interface", 'active_record_querying.html') do %> This guide covers the database query interface provided by Active Record. <% end %> @@ -77,7 +77,7 @@ h3. Digging Deeper This guide covers Rails integration with Rack and interfacing with other Rack components. <% end %> -<% guide("Rails Internationalization API", 'i18n.html', :ticket => 23) do %> +<% guide("Rails Internationalization API", 'i18n.html') do %> 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. <% end %> @@ -117,4 +117,8 @@ h3. Digging Deeper Various caching techniques provided by Rails. <% end %> +<% guide("Contributing to Rails", 'contributing_to_rails.html') do %> + 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. +<% end %> + diff --git a/vendor/rails/railties/guides/source/layout.html.erb b/vendor/rails/railties/guides/source/layout.html.erb index cb02b90e..eb66366d 100644 --- a/vendor/rails/railties/guides/source/layout.html.erb +++ b/vendor/rails/railties/guides/source/layout.html.erb @@ -16,15 +16,15 @@ @@ -63,6 +63,9 @@
The Basics of Creating Rails Plugins
Configuring Rails Applications
Rails on Rack
+
Rails Command Line Tools and Rake Tasks
+
Caching with Rails
+
Contributing to Rails
@@ -92,7 +95,7 @@
diff --git a/vendor/rails/railties/guides/source/layouts_and_rendering.textile b/vendor/rails/railties/guides/source/layouts_and_rendering.textile index 5e2cedcf..69faa1b4 100644 --- a/vendor/rails/railties/guides/source/layouts_and_rendering.textile +++ b/vendor/rails/railties/guides/source/layouts_and_rendering.textile @@ -39,7 +39,7 @@ Rails will automatically render +app/views/books/show.html.erb+ after running th NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), +.rjs+ for RJS (javascript with embedded ruby) and +.builder+ for Builder (XML generator). You'll also find +.rhtml+ used for ERB templates and +.rxml+ for Builder templates, but those extensions are now formally deprecated and will be removed from a future version of Rails. -h4. Using render +h4. Using +render+ In most cases, the +ActionController::Base#render+ method does the heavy lifting of rendering your application's content for use by a browser. There are a variety of ways to customize the behavior of +render+. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well. @@ -140,7 +140,7 @@ NOTE: By default, the file is rendered without using the current layout. If you TIP: If you're running on Microsoft Windows, you should use the +:file+ option to render a file, because Windows filenames do not have the same format as Unix filenames. -h5. Using render with :inline +h5. Using +render+ with +:inline+ The +render+ method can do without a view completely, if you're willing to use the +:inline+ option to supply ERB as part of the method call. This is perfectly valid: @@ -158,7 +158,7 @@ render :inline => "xml.p {'Horrid coding practice!'}", :type => :builder -h5. Using render with :update +h5. Using +render+ with +:update+ You can also render javascript-based page updates inline using the +:update+ option to +render+: @@ -212,7 +212,7 @@ render :js => "alert('Hello Rails');" This will send the supplied string to the browser with a MIME type of +text/javascript+. -h5. Options for render +h5. Options for +render+ Calls to the +render+ method generally accept four options: @@ -221,7 +221,7 @@ Calls to the +render+ method generally accept four options: * +:status+ * +:location+ -h6. The :content_type Option +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: @@ -229,7 +229,7 @@ By default, Rails will serve the results of a rendering operation with the MIME render :file => filename, :content_type => 'application/rss' -h6. The :layout Option +h6. The +:layout+ Option With most of the options to +render+, the rendered content is displayed as part of the current layout. You'll learn more about layouts and how to use them later in this guide. @@ -245,7 +245,7 @@ You can also tell Rails to render with no layout at all: render :layout => false -h6. The :status Option +h6. The +:status+ Option Rails will automatically generate a response with the correct HTML status code (in most cases, this is +200 OK+). You can use the +:status+ option to change this: @@ -256,7 +256,7 @@ render :status => :forbidden Rails understands either numeric status codes or symbols for status codes. You can find its list of status codes in +actionpack/lib/action_controller/status_codes.rb+. You can also see there how Rails maps symbols to status codes. -h6. The :location Option +h6. The +:location+ Option You can use the +:location+ option to set the HTTP +Location+ header: @@ -316,7 +316,7 @@ Now, if the current user is a special user, they'll get a special layout when vi class ProductsController < ApplicationController - layout proc{ |controller| controller. + layout proc { |controller| controller.request.xhr? ? 'popup' : 'application' } # ... end @@ -327,13 +327,12 @@ Layouts specified at the controller level support +:only+ and +:except+ options class ProductsController < ApplicationController - layout "inventory", :only => :index layout "product", :except => [:index, :rss] #... end -With those declarations, the +inventory+ layout would be used only for the +index+ method, the +product+ layout would be used for everything else except the +rss+ method, and the +rss+ method will have its layout determined by the automatic layout rules. +With this declaration, the +product+ layout would be used for everything but the +rss+ and +index+ methods. h6. Layout Inheritance @@ -403,6 +402,7 @@ def show if @book.special? render :action => "special_show" end + render :action => "regular_show" end @@ -414,10 +414,24 @@ def show if @book.special? render :action => "special_show" and return end + render :action => "regular_show" end -h4. Using redirect_to +Note that the implicit render done by ActionController detects if +render+ has been called, and thus avoids this error. So this code will work with problems: + + + def show + @book = Book.find(params[:id]) + if @book.special? + render :action => "special_show" + end + end + + +This will render a book with +special?+ set with the +special_show+ template, while other books will render with the default +show+ template. + +h4. Using +redirect_to+ Another way to handle returning responses to an HTTP request is with +redirect_to+. As you've seen, +render+ tells Rails which view (or other asset) to use in constructing a response. The +redirect_to+ method does something completely different: it tells the browser to send a new request for a different URL. For example, you could redirect from wherever you are in your code to the index of photos in your application with this call: @@ -441,7 +455,7 @@ redirect_to photos_path, :status => 301 Just like the +:status+ option for +render+, +:status+ for +redirect_to+ accepts both numeric and symbolic header designations. -h5. The Difference Between render and redirect +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. @@ -455,7 +469,7 @@ end def show @book = Book.find(params[:id]) if @book.nil? - render :action => "index" and return + render :action => "index" end end @@ -470,14 +484,14 @@ end def show @book = Book.find(params[:id]) if @book.nil? - redirect_to :action => "index" and return + redirect_to :action => "index" end 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. -h4. Using head To Build Header-Only Responses +h4. Using +head+ To Build Header-Only Responses The +head+ method exists to let you send back responses to the browser that have only headers. It provides a more obvious alternative to calling +render :nothing+. The +head+ method takes one response, which is interpreted as a hash of header names and values. For example, you can return only an error header: @@ -514,7 +528,7 @@ You can use these tags in layouts or other views, although the tags other than + 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. -h5. Linking to Feeds with auto_discovery_link_tag +h5. Linking to Feeds with +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: @@ -529,7 +543,7 @@ There are three tag options available for +auto_discovery_link_tag+: * +:type+ specifies an explicit MIME type. Rails will generate an appropriate MIME type automatically * +:title+ specifies the title of the link -h5. Linking to Javascript Files with javascript_include_tag +h5. Linking to Javascript Files with +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+: @@ -588,7 +602,7 @@ By default, the combined file will be delivered as +javascripts/all.js+. You can 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 +stylesheet_link_tag+ The +stylesheet_link_tag+ helper returns an HTML ++ 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.cs+: @@ -647,7 +661,7 @@ By default, the combined file will be delivered as +stylesheets/all.css+. You ca 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 +image_tag+ The +image_tag+ helper builds an HTML +<image>+ tag to the specified file. By default, files are loaded from +public/images+. If you don't specify an extension, +.png+ is assumed by default: @@ -673,7 +687,7 @@ There are also three special options you can use with +image_tag+: * +:size+ specifies both width and height, in the format "{width}x{height}" (for example, "150x125") * +:mouseover+ sets an alternate image to be used when the onmouseover event is fired. -h4. Understanding yield +h4. Understanding +yield+ Within the context of a layout, +yield+ identifies a section where content from the view should be inserted. The simplest way to use this is to have a single +yield+, into which the entire contents of the view currently being rendered is inserted: @@ -702,7 +716,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 +content_for+ The +content_for+ method allows you to insert content into a +yield+ block in your layout. You only use +content_for+ to insert content in named yields. For example, this view would work with the layout that you just saw: @@ -915,7 +929,7 @@ You may find that your application requires a layout that differs slightly from Suppose you have the follow +ApplicationController+ layout: -* +app/views/layouts/application.erb+ +* +app/views/layouts/application.html.erb+ @@ -934,7 +948,7 @@ Suppose you have the follow +ApplicationController+ layout: On pages generated by +NewsController+, you want to hide the top menu and add a right menu: -* +app/views/layouts/news.erb+ +* +app/views/layouts/news.html.erb+ <% content_for :stylesheets do %> diff --git a/vendor/rails/railties/guides/source/migrations.textile b/vendor/rails/railties/guides/source/migrations.textile index 3f1ad510..5ed94c30 100644 --- a/vendor/rails/railties/guides/source/migrations.textile +++ b/vendor/rails/railties/guides/source/migrations.textile @@ -15,7 +15,7 @@ You'll learn all about migrations including: endprologue. -h3. Anatomy Of A Migration +h3. Anatomy of a Migration Before I dive into the details of a migration, here are a few examples of the sorts of things you can do: @@ -60,7 +60,7 @@ to have already opted in, so we use the User model to set the flag to +true+ for NOTE: Some "caveats":#using-models-in-your-migrations apply to using models in your migrations. -h4. Migrations are classes +h4. Migrations are Classes A migration is a subclass of ActiveRecord::Migration that implements two class methods: +up+ (perform the required transformations) and +down+ (revert them). @@ -76,11 +76,11 @@ Active Record provides methods that perform common data definition tasks in a da * +add_index+ * +remove_index+ -If you need to perform tasks specific to your database (for example create a "foreign key":#active-recordand-referential-integrity constraint) then the +execute+ function 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). +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+ function 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), migrations are wrapped in a transaction. If the database does not support this (for example MySQL and SQLite) 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 +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 migration class' name must match (the camelcased version of) the latter part of the file name. For example +20080906120000_create_products.rb+ should define +CreateProducts+ and +20080906120001_add_details_to_products.rb+ should define +AddDetailsToProducts+. If you do feel the need to change the file name then you have to update the name of the class inside or Rails will complain about a missing class. @@ -92,15 +92,15 @@ For example Alice adds migrations +20080906120000+ and +20080906123000+ and Bob 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 +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. Just use some common sense. -h3. Creating A Migration +h3. Creating a Migration -h4. Creating a model +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 those will also be created. For example, running @@ -130,7 +130,7 @@ end 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 +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: @@ -218,7 +218,7 @@ 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 +h4. Creating a Table Migration method +create_table+ will be one of your workhorses. A typical use would be @@ -268,7 +268,7 @@ end This may however hinder portability to other databases. -h4. Changing tables +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 @@ -292,7 +292,7 @@ rename_column :products, :upccode, :upc_code 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 +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: @@ -327,13 +327,13 @@ end 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+ for that or a plugin that adds "foreign key support":#active-recordand-referential-integrity. +NOTE: The +references+ helper does not actually create foreign key constraints for you. You will need to use +execute+ for that 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+ function to execute arbitrary SQL. -For more details and examples of individual methods check the API documentation, in particular the documentation for "ActiveRecord::ConnectionAdapters::SchemaStatements":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "ActiveRecord::ConnectionAdapters::TableDefinition":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "ActiveRecord::ConnectionAdapters::Table":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+). +For more details and examples of individual methods check the API documentation, in particular the documentation for "ActiveRecord::ConnectionAdapters::SchemaStatements":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "ActiveRecord::ConnectionAdapters::TableDefinition":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "ActiveRecord::ConnectionAdapters::Table":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+). -h4. Writing your down method +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 do things in precisely the reverse order to in the +up+ method. For example @@ -384,7 +384,7 @@ rake db:migrate VERSION=20080906120000 If this 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 +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 @@ -410,7 +410,7 @@ Neither of these Rake tasks do anything you could not do with +db:migrate+, they 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":#schemadumpingandyou. +NOTE: This is not the same as running all the migrations - see the section on "schema.rb":#schema-dumping-and-you. h4. Being Specific @@ -422,7 +422,7 @@ rake db:migrate:up VERSION=20080906120000 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 +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 @@ -482,7 +482,7 @@ generates the following output If you just want Active Record to shut up then running +rake db:migrate VERBOSE=false+ will suppress any output. -h3. Using Models In Your Migrations +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. @@ -506,7 +506,7 @@ end The migration has its own minimal copy of the +Product+ model and no longer cares about the +Product+ model defined in the application. -h4. Dealing with changing models +h4. Dealing with Changing Models For performance reasons information about the columns a model has is cached. For example if you add a column to a table and then try and use the corresponding model to insert a new row it may try and use the old column information. You can force Active Record to re-read the column information with the +reset_column_information+ method, for example @@ -528,9 +528,9 @@ end -h3. Schema dumping and you +h3. Schema Dumping and You -h4. What are schema files for? +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. @@ -540,7 +540,7 @@ For example, this is how the test database is created: the current development d 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 all summed up in the schema file. The "annotate_models":http://agilewebdevelopment.com/plugins/annotate_models plugin, which automatically adds (and updates) comments at the top of each model summarising the schema, may also be of interest. -h4. Types of schema dumps +h4. Types of Schema Dumps There are two ways to dump the schema. This is set in +config/environment.rb+ by the +config.active_record.schema_format+ setting, which may be either +:sql+ or +:ruby+. @@ -572,7 +572,7 @@ Instead of using Active Record's schema dumper the database's structure will be By definition this will be a perfect copy of the database's structure but this will usually prevent loading the schema into a database other than the one used to create it. -h4. Schema dumps and source control +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. diff --git a/vendor/rails/railties/guides/source/nested_model_forms.textile b/vendor/rails/railties/guides/source/nested_model_forms.textile new file mode 100644 index 00000000..4b685b21 --- /dev/null +++ b/vendor/rails/railties/guides/source/nested_model_forms.textile @@ -0,0 +1,222 @@ +h2. Rails nested model forms + +Creating a form for a model _and_ its associations can become quite tedious. Therefor Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations. + +In this guide you will: + +* do stuff + +endprologue. + +NOTE: This guide assumes the user knows how to use the "Rails form helpers":form_helpers.html in general. Also, it’s *not* an API reference. For a complete reference please visit "the Rails API documentation":http://api.rubyonrails.org/. + + +h3. Model setup + +To be able to use the nested model functionality in your forms, the model will need to support some basic operations. + +First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The +fields_for+ form helper will look for this method to decide whether or not a nested model form should be build. + +If the associated object is an array a form builder will be yielded for each object, else only a single form builder will be yielded. + +Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the +:address+ attribute, the +fields_for+ form helper will look for a method on the Person instance named +address_attributes=+. + +h4. ActiveRecord::Base model + +For an ActiveRecord::Base model and association this writer method is commonly defined with the +accepts_nested_attributes_for+ class method: + +h5. has_one + + +class Person < ActiveRecord::Base + has_one :address + accepts_nested_attributes_for :address +end + + +h5. belongs_to + + +class Person < ActiveRecord::Base + belongs_to :firm + accepts_nested_attributes_for :firm +end + + +h5. has_many / has_and_belongs_to_many + + +class Person < ActiveRecord::Base + has_many :projects + accepts_nested_attributes_for :projects +end + + +h4. Custom model + +As you might have inflected from this explanation, you _don’t_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behaviour: + +h5. Single associated object + + +class Person + def address + Address.new + end + + def address_attributes=(attributes) + # ... + end +end + + +h5. Association collection + + +class Person + def projects + [Project.new, Project.new] + end + + def projects_attributes=(attributes) + # ... + end +end + + +NOTE: See (TODO) in the advanced section for more information on how to deal with the CRUD operations in your custom model. + +h3. Views + +h4. Controller code + +A nested model form will _only_ be build if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first. + +Consider the following typical RESTful controller which will prepare a new Person instance and its +address+ and +projects+ associations before rendering the +new+ template: + + +class PeopleController < ActionController:Base + def new + @person = Person.new + @person.built_address + 2.times { @person.projects.build } + end + + def create + @person = Person.new(params[:person]) + if @person.save + # ... + end + end +end + + +NOTE: Obviously the instantiation of the associated object(s) can become tedious and not DRY, so you might want to move that into the model itself. ActiveRecord::Base provides an +after_initialize+ callback which is a good way to refactor this. + +h4. Form code + +Now that you have a model instance, with the appropriate methods and associated object(s), you can start building the nested model form. + +h5. Standard form + +Start out with a regular RESTful form: + + +<% form_for @person do |f| %> + <%= f.text_field :name %> +<% end %> + + +This will generate the following html: + + +
+ + + + +h5. Nested form for a single associated object + +Now add a nested form for the +address+ association: + + +<% form_for @person do |f| %> + <%= f.text_field :name %> + + <% f.fields_for :address do |af| %> + <%= f.text_field :street %> + <% end %> +<% end %> + + +This generates: + + +
+ + + + + + +Notice that +fields_for+ recognized the +address+ as an association for which a nested model form should be build by the way it has namespaced the +name+ attribute. + +When this form is posted the Rails parameter parser will construct a hash like the following: + + +{ + "person" => { + "name" => "Eloy Duran", + "address_attributes" => { + "street" => "Nieuwe Prinsengracht" + } + } +} + + +That’s it. The controller will simply pass this hash on to the model from the +create+ action. The model will then handle building the +address+ association for you and automatically save it when the parent (+person+) is saved. + +h5. Nested form for a collection of associated objects + +The form code for an association collection is pretty similar to that of a single associated object: + + +<% form_for @person do |f| %> + <%= f.text_field :name %> + + <% f.fields_for :projects do |pf| %> + <%= f.text_field :name %> + <% end %> +<% end %> + + +Which generates: + + +
+ + + + + + + +As you can see it has generated 2 +project name+ inputs, one for each new +project+ that’s build in the controllers +new+ action. Only this time the +name+ attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as: + + +{ + "person" => { + "name" => "Eloy Duran", + "projects_attributes" => { + "0" => { "name" => "Project 1" }, + "1" => { "name" => "Project 2" } + } + } +} + + +You can basically see the +projects_attributes+ hash as an array of attribute hashes. One for each model instance. + +NOTE: The reason that +fields_for+ constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep. + +TIP: You _can_ however pass an array to the writer method generated by +accepts_nested_attributes_for+ if you're using plain Ruby or some other API access. See (TODO) for more info and example. \ No newline at end of file diff --git a/vendor/rails/railties/guides/source/performance_testing.textile b/vendor/rails/railties/guides/source/performance_testing.textile index c2bf36c8..320a5b84 100644 --- a/vendor/rails/railties/guides/source/performance_testing.textile +++ b/vendor/rails/railties/guides/source/performance_testing.textile @@ -32,7 +32,7 @@ end This example is a simple performance test case for profiling a GET request to the application's homepage. -h4. Generating performance tests +h4. Generating Performance Tests Rails provides a generator called +performance_test+ for creating new performance tests: @@ -95,7 +95,7 @@ class Post < ActiveRecord::Base end -h5. Controller example +h5. Controller Example Because performance tests are a special kind of integration test, you can use the +get+ and +post+ methods in them. @@ -121,9 +121,9 @@ class PostPerformanceTest < ActionController::PerformanceTest end -You can find more details about the +get+ and +post+ methods in the link:../testing_rails_applications.html#mgunderloy[Testing Rails Applications] guide. +You can find more details about the +get+ and +post+ methods in the "Testing Rails Applications":testing.html guide. -h5. Model example +h5. Model Example Even though the performance tests are integration tests and hence closer to the request/response cycle by nature, you can still performance test pure model code. @@ -173,13 +173,13 @@ h4. Metrics Benchmarking and profiling run performance tests in various modes described below. -h5. Wall time +h5. Wall Time Wall time measures the real world time elapsed during the test run. It is affected by any other processes concurrently running on the system. Mode: Benchmarking -h5. Process time +h5. Process Time Process time measures the time taken by the process. It is unaffected by any other processes running concurrently on the same system. Hence, process time is likely to be constant for any given performance test, irrespective of the machine load. @@ -197,19 +197,19 @@ Objects measures the number of objects allocated for the performance test case. Mode: Benchmarking, Profiling "Requires GC Patched Ruby":#installing-gc-patched-ruby -h5. GC runs +h5. GC Runs GC Runs measures the number of times GC was invoked for the performance test case. Mode: Benchmarking "Requires GC Patched Ruby":#installing-gc-patched-ruby -h5. GC time +h5. GC Time GC Time measures the amount of time spent in GC for the performance test case. Mode: Benchmarking "Requires GC Patched Ruby":#installing-gc-patched-ruby -h4. Understanding the output +h4. Understanding the Output Performance tests generate different outputs inside +tmp/performance+ directory depending on their mode and metric. @@ -217,7 +217,7 @@ h5. Benchmarking In benchmarking mode, performance tests generate two types of outputs: -h6. Command line +h6. Command Line This is the primary form of output in benchmarking mode. Example: @@ -230,7 +230,7 @@ BrowsingTest#test_homepage (31 ms warmup) gc_time: 19 ms -h6. CSV files +h6. CSV Files Performance test results are also appended to +.csv+ files inside +tmp/performance+. For example, running the default +BrowsingTest#test_homepage+ will generate following five files: @@ -262,7 +262,7 @@ h5. Profiling In profiling mode, you can choose from four types of output. -h6. Command line +h6. Command Line This is a very basic form of output in profiling mode: @@ -283,15 +283,15 @@ Graph output shows how long each method takes to run, which methods call it and h6. Tree -Tree output is profiling information in calltree format for use by http://kcachegrind.sourceforge.net/html/Home.html[kcachegrind] and similar tools. +Tree output is profiling information in calltree format for use by "kcachegrind":http://kcachegrind.sourceforge.net/html/Home.html and similar tools. -h4. Tuning test runs +h4. Tuning Test Runs By default, each performance test is run +4 times+ in benchmarking mode and +1 time+ in profiling. However, test runs can easily be configured. WARNING: Performance test configurability is not yet enabled in Rails. But it will be soon. -h4. Performance test environment +h4. Performance Test Environment Performance tests are run in the +development+ environment. But running performance tests will set the following configuration parameters: @@ -303,7 +303,7 @@ Rails.logger.level = ActiveSupport::BufferedLogger::INFO As +ActionController::Base.perform_caching+ is set to +true+, performance tests will behave much as they do in the +production+ environment. -h4. Installing GC-patched Ruby +h4. Installing GC-Patched Ruby To get the best from Rails performance tests, you need to build a special Ruby binary with some super powers - "GC patch":http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch for measuring GC Runs/Time and memory/object allocation. @@ -313,7 +313,7 @@ h5. Installation Compile Ruby and apply this "GC Patch":http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch. -h5. Download and extract +h5. Download and Extract [lifo@null ~]$ mkdir rubygc @@ -322,13 +322,13 @@ h5. Download and extract [lifo@null ~]$ cd -h5. Apply the patch +h5. Apply the Patch [lifo@null ruby-version]$ curl http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch | patch -p0 -h5. Configure and install +h5. Configure and Install The following will install ruby in your home directory's +/rubygc+ directory. Make sure to replace ++ with a full patch to your actual home directory. @@ -337,7 +337,7 @@ The following will install ruby in your home directory's +/rubygc+ directory. Ma [lifo@null ruby-version]$ make && make install -h5. Prepare aliases +h5. Prepare Aliases For convenience, add the following lines in your +~/.profile+: @@ -349,7 +349,7 @@ alias gcirb='~/rubygc/bin/irb' alias gcrails='~/rubygc/bin/rails' -h5. Install Rubygems and dependency gems +h5. Install Rubygems and Dependency Gems Download "Rubygems":http://rubyforge.org/projects/rubygems and install it from source. Rubygem's README file should have necessary installation instructions. @@ -446,11 +446,11 @@ This benchmarks the code enclosed in the +Project.benchmark("Creating project") Creating project (185.3ms) -Please refer to the "API docs":http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336 for additional options to +benchmark()+ +Please refer to the "API docs":http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001336 for additional options to +benchmark()+ h4. Controller -Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715 +Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715 def process_projects @@ -465,7 +465,7 @@ NOTE: +benchmark+ is a class method inside controllers h4. View -And in "views":http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715: +And in "views":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715: <% benchmark("Showing projects partial") do %> @@ -496,21 +496,21 @@ Michael Koziarski has an "interesting blog post":http://www.therailsway.com/2009 h3. Useful Links -h4. Rails plugins and gems +h4. Rails Plugins and Gems * "Rails Analyzer":http://rails-analyzer.rubyforge.org -* "Palmist":http://www.flyingmachinestudios.com/projects +* "Palmist":http://www.flyingmachinestudios.com/projects/ * "Rails Footnotes":http://github.com/josevalim/rails-footnotes/tree/master * "Query Reviewer":http://github.com/dsboulder/query_reviewer/tree/master -h4. Generic tools +h4. Generic Tools -* "httperf":http://www.hpl.hp.com/research/linux/httperf +* "httperf":http://www.hpl.hp.com/research/linux/httperf/ * "ab":http://httpd.apache.org/docs/2.2/programs/ab.html -* "JMeter":http://jakarta.apache.org/jmeter +* "JMeter":http://jakarta.apache.org/jmeter/ * "kcachegrind":http://kcachegrind.sourceforge.net/html/Home.html -h4. Tutorials and documentation +h4. Tutorials and Documentation * "ruby-prof API Documentation":http://ruby-prof.rubyforge.org * "Request Profiling Railscast":http://railscasts.com/episodes/98-request-profiling - Outdated, but useful for understanding call graphs diff --git a/vendor/rails/railties/guides/source/plugins.textile b/vendor/rails/railties/guides/source/plugins.textile index d2fb157e..55ecdcd3 100644 --- a/vendor/rails/railties/guides/source/plugins.textile +++ b/vendor/rails/railties/guides/source/plugins.textile @@ -31,7 +31,7 @@ endprologue. h3. Setup -h4. Create the basic app +h4. Create the Basic Application The examples in this guide require that you have a working rails application. To create a simple rails app execute: @@ -49,7 +49,7 @@ Then navigate to http://localhost:3000/birds. Make sure you have a functioning NOTE: The aforementioned instructions will work for sqlite3. For more detailed instructions on how to create a rails app for other databases see the API docs. -h4. Generate the plugin skeleton +h4. Generate the Plugin Skeleton Rails ships with a plugin generator which creates a basic plugin skeleton. Pass the plugin name, either 'CamelCased' or 'under_scored', as an argument. Pass +--with-generator+ to add an example generator also. @@ -91,7 +91,7 @@ create vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb create vendor/plugins/yaffle/generators/yaffle/USAGE -h4. Organize your files +h4. Organize Your Files To make it easy to organize your files and to make the plugin more compatible with GemPlugins, start out by altering your file system to look like this: @@ -211,7 +211,7 @@ end Now whenever you write a test that requires the database, you can call 'load_schema'. -h4. Run the plugin tests +h4. Run the Plugin Tests Once you have these files in place, you can write your first test to ensure that your plugin-testing setup is correct. By default rails generates a file in 'vendor/plugins/yaffle/test/yaffle_test.rb' with a sample test. Replace the contents of that file with: @@ -275,7 +275,7 @@ rake DB=postgresql Now you are ready to test-drive your plugin! -h3. Extending core classes +h3. Extending Core Classes This section will explain how to add a method to String that will be available anywhere in your rails app. @@ -339,7 +339,7 @@ $ ./script/console => "squawk! Hello World" -h4. Working with init.rb +h4. Working with +init.rb+ When rails loads plugins it looks for the file named 'init.rb' or 'rails/init.rb'. However, when the plugin is initialized, 'init.rb' is invoked via +eval+ (not +require+) so it has slightly different behavior. @@ -369,7 +369,7 @@ class ::Hash end -h3. Add an 'acts_as' method to Active Record +h3. Add an "acts_as" Method to Active Record A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you want to write a method called 'acts_as_yaffle' that adds a 'squawk' method to your models. @@ -425,7 +425,7 @@ end With structure you can easily separate the methods that will be used for the class (like +Hickwall.some_method+) and the instance (like +@hickwell.some_method+). -h4. Add a class method +h4. Add a Class Method This plugin will expect that you've added a method to your model named 'last_squawk'. However, the plugin users might have already defined a method on their model named 'last_squawk' that they use for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'. @@ -478,7 +478,7 @@ end ActiveRecord::Base.send :include, Yaffle -h4. Add an instance method +h4. Add an Instance Method This plugin will add a method named 'squawk' to any Active Record objects that call 'acts_as_yaffle'. The 'squawk' method will simply set the value of one of the fields in the database. @@ -800,7 +800,7 @@ Many plugins ship with generators. When you created the plugin above, you speci Building generators is a complex topic unto itself and this section will cover one small aspect of generators: generating a simple text file. -h4. Testing generators +h4. Testing Generators Many rails plugin authors do not test their generators, however testing generators is quite simple. A typical generator test does the following: @@ -864,7 +864,7 @@ class YaffleDefinitionGenerator < Rails::Generator::Base end -h4. The USAGE file +h4. The +USAGE+ File If you plan to distribute your plugin, developers will expect at least a minimum of documentation. You can add simple documentation to the generator by updating the USAGE file. @@ -891,7 +891,7 @@ Description: Adds a file with the definition of a Yaffle to the app's main directory -h3. Add a custom generator command +h3. Add a Custom Generator Command You may have noticed above that you can used one of the built-in rails migration commands +migration_template+. If your plugin needs to add and remove lines of text from existing files you will need to write your own generator methods. @@ -1144,7 +1144,7 @@ end Here are a few possibilities for how to allow developers to use your plugin migrations: -h4. Create a custom rake task +h4. Create a Custom Rake Task * *vendor/plugins/yaffle/tasks/yaffle_tasks.rake:* @@ -1165,7 +1165,7 @@ namespace :db do end -h4. Call migrations directly +h4. Call Migrations Directly * *vendor/plugins/yaffle/lib/yaffle.rb:* @@ -1191,7 +1191,7 @@ end NOTE: several plugin frameworks such as Desert and Engines provide more advanced plugin functionality. -h4. Generate migrations +h4. Generate Migrations Generating migrations has several advantages over other methods. Namely, you can allow other developers to more easily customize the migration. The flow looks like this: @@ -1417,7 +1417,7 @@ h4. References * http://www.mbleigh.com/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins * http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2. -h4. Contents of 'lib/yaffle.rb' +h4. Contents of +lib/yaffle.rb+ * *vendor/plugins/yaffle/lib/yaffle.rb:* @@ -1440,7 +1440,7 @@ end # end -h4. Final plugin directory structure +h4. Final Plugin Directory Structure The final plugin should have a directory structure that looks something like this: diff --git a/vendor/rails/railties/guides/source/rails_on_rack.textile b/vendor/rails/railties/guides/source/rails_on_rack.textile index e300e047..07ca1624 100644 --- a/vendor/rails/railties/guides/source/rails_on_rack.textile +++ b/vendor/rails/railties/guides/source/rails_on_rack.textile @@ -15,7 +15,7 @@ h3. Introduction to Rack bq. Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call. -- "Rack API Documentation":http://rack.rubyforge.org/doc +- "Rack API Documentation":http://rack.rubyforge.org/doc/ Explaining Rack is not really in the scope of this guide. In case you are not familiar with Rack's basics, you should check out the following links: @@ -30,7 +30,7 @@ h4. Rails Application's Rack Object ActionController::Dispatcher.new is the primary Rack application object of a Rails application. Any Rack compliant web server should be using +ActionController::Dispatcher.new+ object to serve a Rails application.

-h4. script/server +h4. +script/server+ script/server does the basic job of creating a +Rack::Builder+ object and starting the webserver. This is Rails' equivalent of Rack's +rackup+ script. @@ -55,7 +55,7 @@ Middlewares used in the code above are primarily useful only in the development |Rails::Rack::Static|Serves static files inside +RAILS_ROOT/public+ directory| |Rails::Rack::Debugger|Starts Debugger| -h4. rackup +h4. +rackup+ To use +rackup+ instead of Rails' +script/server+, you can put the following inside +config.ru+ of your Rails application's root directory: @@ -119,9 +119,7 @@ h5. Adding a Middleware You can add a new middleware to the middleware stack using any of the following methods: -* +config.middleware.add(new_middleware, args)+ - Adds the new middleware at the bottom of the middleware stack. - -* +config.middleware.insert(index, new_middleware, args)+ - Adds the new middleware at the position specified by +index+ in the middleware stack. +* +config.middleware.use(new_middleware, args)+ - Adds the new middleware at the bottom of the middleware stack. * +config.middleware.insert_before(existing_middleware, new_middleware, args)+ - Adds the new middleware before the specified existing middleware in the middleware stack. @@ -153,6 +151,16 @@ You can swap an existing middleware in the middleware stack using +config.middle config.middleware.swap ActionController::Failsafe, Lifo::Failsafe +h5. Middleware Stack is an Array + +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. + +For example, the following removes the middleware matching the supplied class name: + + +config.middleware.delete(middleware) + + h4. Internal Middleware Stack Much of Action Controller's functionality is implemented as Middlewares. The following table explains the purpose of each of them: @@ -197,10 +205,32 @@ use Rack::Head run ActionController::Dispatcher.new +h4. Using Rack Builder + +The following shows how to replace use +Rack::Builder+ instead of the Rails supplied +MiddlewareStack+. + +Clear the existing Rails middleware stack + + +# environment.rb +config.middleware.clear + + +
+Add a +config.ru+ file to +RAILS_ROOT+ + + +# config.ru +use MyOwnStackFromStratch +run ActionController::Dispatcher.new + + h3. Rails Metal Applications Rails Metal applications are minimal Rack applications specially designed for integrating with a typical Rails application. As Rails Metal Applications skip all of the Action Controller stack, serving a request has no overhead from the Rails framework itself. This is especially useful for infrequent cases where the performance of the full stack Rails framework is an issue. +Ryan Bates' railscast on the "Rails Metal":http://railscasts.com/episodes/150-rails-metal provides a nice walkthrough generating and using Rails Metal. + h4. Generating a Metal Application Rails provides a generator called +metal+ for creating a new Metal application: @@ -226,6 +256,8 @@ class Poller end +Metal applications within +app/metal+ folders in plugins will also be discovered and added to the list + Metal applications are an optimization. You should make sure to "understand the related performance implications":http://weblog.rubyonrails.org/2008/12/20/performance-of-rails-metal before using it. h4. Execution Order @@ -244,10 +276,31 @@ def call(env) end -In the code above, +@metals+ is an ordered ( alphabetical ) hash of metal applications. Due to the alphabetical ordering, +aaa.rb+ will come before +bbb.rb+ in the metal chain. +In the code above, +@metals+ is an ordered hash of metal applications. Due to the default alphabetical ordering, +aaa.rb+ will come before +bbb.rb+ in the metal chain. + +It is, however, possible to override the default ordering in your environment. Simply add a line like the following to +config/environment.rb+ + + +config.metals = ["Bbb", "Aaa"] + + +Each string in the array should be the name of your metal class. If you do this then be warned that any metal applications not listed will not be loaded. WARNING: Metal applications cannot return the HTTP Status +404+ to a client, as it is used for continuing the Metal chain execution. Please use normal Rails controllers or a custom middleware if returning +404+ is a requirement. +h3. Resources + +h4. Learning Rack + +* "Official Rack Website":http://rack.github.com +* "Introducing Rack":http://chneukirchen.org/blog/archive/2007/02/introducing-rack.html +* "Ruby on Rack #1 - Hello Rack!":http://m.onkey.org/2008/11/17/ruby-on-rack-1 +* "Ruby on Rack #2 - The Builder":http://m.onkey.org/2008/11/18/ruby-on-rack-2-rack-builder + +h4. Understanding Middlewares + +* "Railscast on Rack Middlewares":http://railscasts.com/episodes/151-rack-middleware + h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/58 diff --git a/vendor/rails/railties/guides/source/routing.textile b/vendor/rails/railties/guides/source/routing.textile index c26a5cd6..a4d9e140 100644 --- a/vendor/rails/railties/guides/source/routing.textile +++ b/vendor/rails/railties/guides/source/routing.textile @@ -39,7 +39,7 @@ Then the routing engine is the piece that translates that to a link to a URL suc NOTE: Patient needs to be declared as a resource for this style of translation via a named route to be available. -h3. Quick Tour of Routes.rb +h3. Quick Tour of +routes.rb+ There are two components to routing in Rails: the routing engine itself, which is supplied as part of Rails, and the file +config/routes.rb+, which contains the actual routes that will be used by your application. Learning exactly what you can put in +routes.rb+ is the main topic of this guide, but before we dig in let's get a quick overview. @@ -221,7 +221,7 @@ Although the conventions of RESTful routing are likely to be sufficient for many You can also add additional routes via the +:member+ and +:collection+ options, which are discussed later in this guide. -h5. Using :controller +h5. Using +:controller+ The +:controller+ option lets you use a controller name that is different from the public-facing resource name. For example, this routing entry: @@ -270,7 +270,7 @@ end That would give you routing for +admin/photos+ and +admin/videos+ controllers. -h5. Using :singular +h5. Using +:singular+ If for some reason Rails isn't doing what you want in converting the plural resource name to a singular name in member routes, you can override its judgment with the +:singular+ option: @@ -280,9 +280,9 @@ map.resources :teeth, :singular => "tooth" TIP: Depending on the other code in your application, you may prefer to add additional rules to the +Inflector+ class instead. -h5. Using :requirements +h5. Using +:requirements+ -You an use the +:requirements+ option in a RESTful route to impose a format on the implied +:id+ parameter in the singular routes. For example: +You can use the +:requirements+ option in a RESTful route to impose a format on the implied +:id+ parameter in the singular routes. For example: map.resources :photos, :requirements => {:id => /[A-Z][A-Z][0-9]+/} @@ -290,11 +290,11 @@ map.resources :photos, :requirements => {:id => /[A-Z][A-Z][0-9]+/} This declaration constrains the +:id+ parameter to match the supplied regular expression. So, in this case, +/photos/1+ would no longer be recognized by this route, but +/photos/RR27+ would. -h5. Using :conditions +h5. Using +:conditions+ Conditions in Rails routing are currently used only to set the HTTP verb for individual routes. Although in theory you can set this for RESTful routes, in practice there is no good reason to do so. (You'll learn more about conditions in the discussion of classic routing later in this guide.) -h5. Using :as +h5. Using +:as+ The +:as+ option lets you override the normal naming for the actual generated paths. For example: @@ -315,7 +315,7 @@ will recognize incoming URLs containing +image+ but route the requests to the Ph NOTE: The helpers will be generated with the name of the resource, not the path name. So in this case, you'd still get +photos_path+, +new_photo_path+, and so on. -h5. Using :path_names +h5. Using +:path_names+ The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs: @@ -338,7 +338,7 @@ TIP: If you find yourself wanting to change this option uniformly for all of you config.action_controller.resources_path_names = { :new => 'make', :edit => 'change' } -h5. Using :path_prefix +h5. Using +:path_prefix+ The +:path_prefix+ option lets you add additional parameters that will be prefixed to the recognized paths. For example, suppose each photo in your application belongs to a particular photographer. In that case, you might declare this route: @@ -357,7 +357,7 @@ NOTE: In most cases, it's simpler to recognize URLs of this sort by creating nes NOTE: You can also use +:path_prefix+ with non-RESTful routes. -h5. Using :name_prefix +h5. Using +:name_prefix+ You can use the :name_prefix option to avoid collisions between routes. This is most useful when you have two resources with the same name that use +:path_prefix+ to map differently. For example: @@ -372,7 +372,7 @@ This combination will give you route helpers such as +photographer_photos_path+ NOTE: You can also use +:name_prefix+ with non-RESTful routes. -h5. Using :only and :except +h5. Using +:only+ and +:except+ By default, Rails creates routes for all seven of the default actions (index, show, new, create, edit, update, and destroy) for every RESTful route in your application. You can use the +:only+ and +:except+ options to fine-tune this behavior. The +:only+ option specifies that only certain routes should be generated: @@ -432,7 +432,7 @@ In addition to the routes for magazines, this declaration will also create route This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+. -h5. Using :name_prefix +h5. Using +:name_prefix+ The +:name_prefix+ option overrides the automatically-generated prefix in nested route helpers. For example, @@ -457,7 +457,7 @@ ads_url(@magazine) edit_ad_path(@magazine, @ad) -h5. Using :has_one and :has_many +h5. Using +:has_one+ and +:has_many+ The +:has_one+ and +:has_many+ options provide a succinct notation for simple nested routes. Use +:has_one+ to nest a singleton resource, or +:has_many+ to nest a plural resource: @@ -748,7 +748,7 @@ end The importance of +map.with_options+ has declined with the introduction of RESTful routes. -h3. Formats and respond_to +h3. Formats and +respond_to+ There's one more way in which routing can do different things depending on differences in the incoming HTTP request: by issuing a response that corresponds to what the request specifies that it will accept. In Rails routing, you can control this with the special +:format+ parameter in the route. @@ -796,7 +796,7 @@ h3. The Empty Route Don't confuse the default routes with the empty route. The empty route has one specific purpose: to route requests that come in to the root of the web site. For example, if your site is example.com, then requests to +http://example.com+ or +http://example.com/+ will be handled by the empty route. -h4. Using map.root +h4. Using +map.root+ The preferred way to set up the empty route is with the +map.root+ command: @@ -829,7 +829,7 @@ h3. Inspecting and Testing Routes Routing in your application should not be a "black box" that you never open. Rails offers built-in tools for both inspecting and testing routes. -h4. Seeing Existing Routes with rake +h4. Seeing Existing Routes with +rake+ If you want a complete list of all of the available routes in your application, run the +rake routes+ command. This will dump all of your routes to the console, in the same order that they appear in +routes.rb+. For each route, you'll see: @@ -851,7 +851,7 @@ TIP: You'll find that the output from +rake routes+ is much more readable if you h4. Testing Routes -Routes should be included in your testing strategy (just like the rest of your application). Rails offers three "built-in assertions":http://api.rubyonrails.com/classes/ActionController/Assertions/RoutingAssertions.html designed to make testing routes simpler: +Routes should be included in your testing strategy (just like the rest of your application). Rails offers three "built-in assertions":http://api.rubyonrails.org/classes/ActionController/Assertions/RoutingAssertions.html designed to make testing routes simpler: * +assert_generates+ * +assert_recognizes+ diff --git a/vendor/rails/railties/guides/source/security.textile b/vendor/rails/railties/guides/source/security.textile index 5797eb88..1b64cc1b 100644 --- a/vendor/rails/railties/guides/source/security.textile +++ b/vendor/rails/railties/guides/source/security.textile @@ -23,13 +23,13 @@ The Gartner Group however estimates that 75% of attacks are at the web applicati The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at. -In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the Additional Resources chapter). I do it manually because that‘s how you find the nasty logical security problems. +In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the Additional Resources chapter). I do it manually because that‘s how you find the nasty logical security problems. h3. Sessions A good place to start looking at security is with sessions, which can be vulnerable to particular attacks. -h4. What are sessions? +h4. What are Sessions? -- _HTTP is a stateless protocol. Sessions make it stateful._ @@ -49,7 +49,7 @@ h4. Session id A session id consists of the hash value of a random string. The random string is the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a random number) and a constant string. Currently it is not feasible to brute-force Rails' session ids. To date MD5 is uncompromised, but there have been collisions, so it is theoretically possible to create another input text with the same hash value. But this has had no security impact to date. -h4. Session hijacking +h4. Session Hijacking -- _Stealing a user's session id lets an attacker use the web application in the victim's name._ @@ -67,7 +67,7 @@ Hence, the cookie serves as temporary authentication for the web application. Ev The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from $10–$1000 (depending on the available amount of funds), $0.40–$20 for credit card numbers, $1–$8 for online auction site accounts and $4–$30 for email passwords, according to the "Symantec Global Internet Security Threat Report":http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf. -h4. Session guidelines +h4. Session Guidelines -- _Here are some general guidelines on sessions._ @@ -77,7 +77,7 @@ This will also be a good idea, if you modify the structure of an object and old * _(highlight)Critical data should not be stored in session_. If the user clears his cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data. -h4. Session storage +h4. Session Storage -- _Rails provides several storage mechanisms for the session hashes. The most important are ActiveRecordStore and CookieStore._ @@ -100,7 +100,7 @@ config.action_controller.session = { There are, however, derivatives of CookieStore which encrypt the session hash, so the client cannot see it. -h4. Replay attacks for CookieStore sessions +h4. Replay Attacks for CookieStore Sessions -- _Another sort of attack you have to be aware of when using CookieStore is the replay attack._ @@ -116,7 +116,7 @@ Including a nonce (a random value) in the session solves replay attacks. A nonce The best _(highlight)solution against it is not to store this kind of data in a session, but in the database_. In this case store the credit in the database and the logged_in_user_id in the session. -h4. Session fixation +h4. Session Fixation -- _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._ @@ -131,7 +131,7 @@ This attack focuses on fixing a user's session id known to the attacker, and for # As the new trap session is unused, the web application will require the user to authenticate. # From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack. -h4. Session fixation – Countermeasures +h4. Session Fixation – Countermeasures -- _One line of code will protect you from session fixation._ @@ -145,7 +145,7 @@ If you use the popular RestfulAuthentication plugin for user management, add res Another countermeasure is to _(highlight)save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _(highlight)These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way. -h4. Session expiry +h4. Session Expiry -- _Sessions that never expire extend the time-frame for attacks such as cross-site reference forgery (CSRF), session hijacking and session fixation._ @@ -172,7 +172,7 @@ self.delete_all "updated_at < '#{time.to_s(:db)}' OR created_at < '#{2.days.ago.to_s(:db)}'" -h3. Cross-Site Reference Forgery (CSRF) +h3. Cross-Site Request Forgery (CSRF) -- _This attack method works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands._ @@ -278,7 +278,7 @@ Another redirection and self-contained XSS attack works in Firefox and Opera by This example is a Base64 encoded JavaScript which displays a simple message box. In a redirection URL, an attacker could redirect to this URL with the malicious code in it. As a countermeasure, _(highlight)do not allow the user to supply (parts of) the URL to be redirected to_. -h4. File uploads +h4. File Uploads -- _Make sure file uploads don't overwrite important files, and process media files asynchronously._ @@ -303,7 +303,7 @@ A significant disadvantage of synchronous processing of file uploads (as the att The solution to this is best to _(highlight)process media files asynchronously_: Save the media file and schedule a processing request in the database. A second process will handle the processing of the file in the background. -h4. Executable code in file uploads +h4. Executable Code in File Uploads -- _Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory._ @@ -311,7 +311,7 @@ The popular Apache web server has an option called DocumentRoot. This is the hom _(highlight)If your Apache DocumentRoot points to Rails' /public directory, do not put file uploads in it_, store files at least one level downwards. -h4. File downloads +h4. File Downloads -- _Make sure users cannot download arbitrary files._ @@ -333,11 +333,11 @@ send_file filename, :disposition => 'inline' Another (additional) approach is to store the file names in the database and name the files on the disk after the ids in the database. This is also a good approach to avoid possible code in an uploaded file to be executed. The attachment_fu plugin does this in a similar way. -h3. Intranet and Admin security +h3. Intranet and Admin Security -- _Intranet and administration interfaces are popular attack targets, because they allow privileged access. Although this would require several extra-security measures, the opposite is the case in the real world._ -In 2007 there was the first tailor-made "Trojan":http://www.symantec.com/enterprise/security_response/weblog/2007/08/a_monster_trojan.html which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.
 +In 2007 there was the first tailor-made trojan which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.
 *XSS* If your application re-displays malicious user input from the extranet, the application will be vulnerable to XSS. User names, comments, spam reports, order addresses are just a few uncommon examples, where there can be XSS. @@ -347,15 +347,15 @@ Refer to the Injection section for countermeasures against XSS. It is _(highligh *CSRF* Cross-Site Reference Forgery (CSRF) is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface. -A real-world example is a "router reconfiguration by CSRF":http://www.symantec.com/enterprise/security_response/weblog/2008/01/driveby_pharming_in_the_
wild.html. The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen. +A real-world example is a "router reconfiguration by CSRF":http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352. The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen. -Another example changed Google Adsense's e-mail address and password by "CSRF":http://www.0x000000.com/index.php?i=213&bin=11010101. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change his credentials.
 +Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change his credentials.
 Another popular attack is to spam your web application, your blog or forum to propagate malicious XSS. Of course, the attacker has to know the URL structure, but most Rails URLs are quite straightforward or they will be easy to find out, if it is an open-source application's admin interface. The attacker may even do 1,000 lucky guesses by just including malicious IMG-tags which try every possible combination. For _(highlight)countermeasures against CSRF in administration interfaces and Intranet applications, refer to the countermeasures in the CSRF section_. -h4. Additional precautions +h4. Additional Precautions The common admin interface works like this: it's located at www.example.com/admin, may be accessed only if the admin flag is set in the User model, re-displays user input and allows the admin to delete/add/edit whatever data desired. Here are some thoughts about this: @@ -365,7 +365,7 @@ The common admin interface works like this: it's located at www.example.com/admi * _(highlight)Put the admin interface to a special sub-domain_ such as admin.application.com and make it a separate application with its own user management. This makes stealing an admin cookie from the usual domain, www.application.com, impossible. This is because of the same origin policy in your browser: An injected (XSS) script on www.application.com may not read the cookie for admin.application.com and vice-versa. -h3. Mass assignment +h3. Mass Assignment -- _Without any precautions Model.new(params[:model]) allows attackers to set any database column's value._ @@ -392,15 +392,31 @@ 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 attributes= 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: + + + class Person < ActiveRecord::Base + has_many :credits + + accepts_nested_attributes_for :children + end + + class Child < ActiveRecord::Base + belongs_to :person + end + + +As a result, the vulnerability is extended beyond simply exposing column assignment, allowing attackers the ability to create entirely new records in referenced tables (children in this case). + h4. Countermeasures -To avoid this, Rails provides two class methods in your ActiveRecord class to control access to your attributes. The attr_protected method takes a list of attributes that will not be accessible for mass-assignment. For example: +To avoid this, Rails provides two class methods in your Active Record class to control access to your attributes. The +attr_protected+ method takes a list of attributes that will not be accessible for mass-assignment. For example: attr_protected :admin -A much better way, because it follows the whitelist-principle, is the +attr_accessible+ method. It is the exact opposite of attr_protected, because _(highlight)it takes a list of attributes that will be accessible_. All other attributes will be protected. This way you won't forget to protect attributes when adding new ones in the course of development. Here is an example: +A much better way, because it follows the whitelist-principle, is the +attr_accessible+ method. It is the exact opposite of +attr_protected+, because _(highlight)it takes a list of attributes that will be accessible_. All other attributes will be protected. This way you won't forget to protect attributes when adding new ones in the course of development. Here is an example: attr_accessible :name @@ -416,7 +432,15 @@ params[:user] #=> {:name => "ow3ned", :admin => true} @user.admin #=> true -h3. User management +A more paranoid technique to protect your whole project would be to enforce that all models whitelist their accessible attributes. This can be easily achieved with a very simple initializer: + + +ActiveRecord::Base.send(:attr_accessible, nil) + + +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 accessible parameters by using an +attr_accessible+ declaration. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to insert this initializer, run your tests, and expose each attribute (via +attr_accessible+) as dictated by your failing tests. + +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._ @@ -443,7 +467,7 @@ SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1 And thus it found the first user in the database, returned it and logged him in. You can find out more about it in "my blog post":http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/. _(highlight)It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this. -h4. Brute-forcing accounts +h4. Brute-Forcing Accounts -- _Brute-force attacks on accounts are trial and error attacks on the login credentials. Fend them off with more generic error messages and possibly require to enter a CAPTCHA._ @@ -455,7 +479,7 @@ However, what most web application designers neglect, are the forgot-password pa In order to mitigate such attacks, _(highlight)display a generic error message on forgot-password pages, too_. Moreover, you can _(highlight)require to enter a CAPTCHA after a number of failed logins from a certain IP address_. Note, however, that this is not a bullet-proof solution against automatic programs, because these programs may change their IP address exactly as often. However, it raises the barrier of an attack. -h4. Account hijacking +h4. Account Hijacking -- _Many web applications make it easy to hijack user accounts. Why not be different and make it more difficult?_ @@ -508,7 +532,7 @@ By default, Rails logs all requests being made to the web application. But log f filter_parameter_logging :password -h4. Good passwords +h4. Good Passwords -- _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._ @@ -520,7 +544,7 @@ It is interesting that only 4% of these passwords were dictionary words and the A good password is a long alphanumeric combination of mixed cases. As this is quite hard to remember, it is advisable to enter only the _(highlight)first letters of a sentence that you can easily remember_. For example "The quick brown fox jumps over the lazy dog" will be "Tqbfjotld". Note that this is just an example, you should not use well known phrases like these, as they might appear in cracker dictionaries, too. -h4. Regular expressions +h4. Regular Expressions -- _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._ @@ -544,7 +568,7 @@ Whereas %0A is a line feed in URL encoding, so Rails automatically converts it t /\A[\w\.\-\+]+\z/ -h4. Privilege escalation +h4. Privilege Escalation -- _Changing a single parameter may give the user unauthorized access. Remember that every parameter may be changed, no matter how much you hide or obfuscate it._ @@ -605,7 +629,7 @@ SELECT * FROM projects WHERE name = '' OR 1 --' The two dashes start a comment ignoring everything after it. So the query returns all records from the projects table including those blind to the user. This is because the condition is true for all records. -h5. Bypassing authorization +h5. Bypassing Authorization Usually a web application includes access control. The user enters his login credentials, the web applications tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user. @@ -621,7 +645,7 @@ SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' This will simply find the first record in the database, and grants access to this user. -h5. Unauthorized reading +h5. Unauthorized Reading The UNION statement connects two SQL queries and returns the data in one set. An attacker can use it to read arbitrary data from the database. Let's take the example from above: @@ -668,7 +692,7 @@ h4. Cross-Site Scripting (XSS) -- _The most widespread, and one of the most devastating security vulnerabilities in web applications is XSS. This malicious attack injects client-side executable code. Rails provides helper methods to fend these attacks off._ -h5. Entry points +h5. Entry Points An entry point is a vulnerable URL and its parameters where an attacker can start an attack. @@ -676,7 +700,7 @@ The most common entry points are message posts, user comments, and guest books, XSS attacks work like this: An attacker injects some code, the web application saves it and displays it on a page, later presented to a victim. Most XSS examples simply display an alert box, but it is more powerful than that. XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display advertisements for the benefit of the attacker, change elements on the web site to get confidential information or install malicious software through security holes in the web browser. -During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The "Symantec Global Internet Security threat report":http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf also documented 239 browser plug-in vulnerabilities in the last six months of 2007. "Mpack":http://pandalabs.pandasecurity.com/archive/MPack-uncovered_2100_.aspx is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites "were hacked":http://www.0x000000.com/?i=556 like this, among them the British government, United Nations, and many more high targets. +During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The "Symantec Global Internet Security threat report":http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf also documented 239 browser plug-in vulnerabilities in the last six months of 2007. "Mpack":http://pandalabs.pandasecurity.com/archive/MPack-uncovered_2100_.aspx is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites were hacked like this, among them the British government, United Nations, and many more high targets. A relatively new, and unusual, form of entry points are banner advertisements. In earlier 2008, malicious code appeared in banner ads on popular sites, such as MySpace and Excite, according to "Trend Micro":http://blog.trendmicro.com/myspace-excite-and-blick-serve-up-malicious-banner-ads/. @@ -697,7 +721,7 @@ This JavaScript code will simply display an alert box. The next examples do exac
-h6. Cookie theft +h6. Cookie Theft These examples don't do any harm so far, so let's see how an attacker can steal the user's cookie (and thus hijack the user's session). In JavaScript you can use the document.cookie property to read and write the document's cookie. JavaScript enforces the same origin policy, that means a script from one domain cannot access cookies of another domain. The document.cookie property holds the cookie of the originating web server. However, you can read and write this property, if you embed the code directly in the HTML document (as it happens with XSS). Inject this anywhere in your web application to see your own cookie on the result page: @@ -727,7 +751,7 @@ With web page defacement an attacker can do a lot of things, for example, presen -This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iFrame is taken from an "actual attack":http://www.symantec.com/enterprise/security_response/weblog/2007/06/italy_under_attack_mpack_gang.html on legitimate Italian sites using the "Mpack attack framework":http://isc.sans.org/diary.html?storyid=3015. Mpack tries to install malicious software through security holes in the web browser – very successfully, 50% of the attacks succeed. +This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iframe is taken from an actual attack on legitimate Italian sites using the "Mpack attack framework":http://isc.sans.org/diary.html?storyid=3015. Mpack tries to install malicious software through security holes in the web browser – very successfully, 50% of the attacks succeed. A more specialized attack could overlap the entire web site or display a login form, which looks the same as the site's original, but transmits the user name and password to the attackers site. Or it could use CSS and/or JavaScript to hide a legitimate link in the web application, and display another one at its place which redirects to a fake web site. @@ -772,7 +796,7 @@ Network traffic is mostly based on the limited Western alphabet, so new characte This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus “get to know your enemy”, is the "Hackvertor":http://www.businessinfo.co.uk/labs/hackvertor/hackvertor.php. Rails‘ sanitize() method does a good job to fend off encoding attacks. -h5. Examples from the underground +h5. Examples from the Underground _In order to understand today's attacks on web applications, it's best to take a look at some real-world attack vectors._ @@ -786,7 +810,7 @@ The following is an excerpt from the "Js.Yamanner@m":http://www.symantec.com/sec The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application. -Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details and a video demonstration on "Rosario Valotta's website":http://rosario.valotta.googlepages.com/home. Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with. +Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on "Rosario Valotta's paper":http://www.xssed.com/article/9/Paper_A_PoC_of_a_cross_webmail_worm_XWW_called_Njuda_connection/. Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with. In December 2006, 34,000 actual user names and passwords were stolen in a "MySpace phishing attack":http://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html. The idea of the attack was to create a profile page named “login_home_index_html”, so the URL looked very convincing. Specially-crafted HTML and CSS was used to hide the genuine MySpace content from the page and instead display its own login form. @@ -834,11 +858,10 @@ This example, again, showed that a blacklist filter is never complete. However, h4. Textile Injection --- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://whytheluckystiff.net/ruby/redcloth/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS._ +-- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://redcloth.org/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS._ For example, RedCloth translates +_test_+ to <em>test<em>, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS. Get the "all-new version 4":http://www.redcloth.org that removed serious bugs. However, even that version has "some security bugs":http://www.rorsecurity.info/journal/2008/10/13/new-redcloth-security.html, so the countermeasures still apply. Here is an example for version 3.0.4: - RedCloth.new('').to_html # => "" @@ -947,7 +970,7 @@ Content-Type: text/html Under certain circumstances this would present the malicious HTML to the victim. However, this seems to work with Keep-Alive connections, only (and many browsers are using one-time connections). But you can't rely on this. _(highlight)In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks._ -h3. Additional resources +h3. Additional Resources The security landscape shifts and it is important to keep up to date, because missing a new vulnerability can be catastrophic. You can find additional resources about (Rails) security here: @@ -955,7 +978,6 @@ The security landscape shifts and it is important to keep up to date, because mi * Subscribe to the Rails security "mailing list":http://groups.google.com/group/rubyonrails-security * "Keep up to date on the other application layers":http://secunia.com/ (they have a weekly newsletter, too) * A "good security blog":http://ha.ckers.org/blog/ including the "Cross-Site scripting Cheat Sheet":http://ha.ckers.org/xss.html -* Another "good security blog":http://www.0x000000.com/ with some Cheat Sheets, too h3. Changelog diff --git a/vendor/rails/railties/guides/source/testing.textile b/vendor/rails/railties/guides/source/testing.textile index 9b764806..12fc836e 100644 --- a/vendor/rails/railties/guides/source/testing.textile +++ b/vendor/rails/railties/guides/source/testing.textile @@ -20,7 +20,7 @@ h3. Introduction to Testing Testing support was woven into the Rails fabric from the beginning. It wasn't an "oh! let's bolt on support for running tests because they're new and cool" epiphany. Just about every Rails application interacts heavily with a database - and, as a result, your tests will need a database to interact with as well. To write efficient tests, you'll need to understand how to set up this database and populate it with sample data. -h4. The 3 Environments +h4. The Three Environments Every Rails application you build has 3 sides: a side for production, a side for development, and a side for testing. @@ -52,7 +52,7 @@ h4. The Low-Down on Fixtures For good tests, you'll need to give some thought to setting up test data. In Rails, you can handle this by defining and customizing fixtures. -h5. What Are Fixtures? +h5. What are Fixtures? _Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and assume one of two formats: *YAML* or *CSV*. In this guide we will use *YAML* which is the preferred format. @@ -110,9 +110,9 @@ h5. Fixtures in Action Rails by default automatically loads all fixtures from the 'test/fixtures' folder for your unit and functional test. Loading involves three steps: - * Remove any existing data from the table corresponding to the fixture - * Load the fixture data into the table - * Dump the fixture data into a variable in case you want to access it directly +* Remove any existing data from the table corresponding to the fixture +* 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 @@ -140,9 +140,9 @@ h3. Unit Testing your Models In Rails, unit tests are what you write to test your models. -For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practises. I will be using examples from this generated code and would be supplementing it with additional examples where necessary. +For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. I will be using examples from this generated code and would be supplementing it with additional examples where necessary. -NOTE: For more information on Rails _scaffolding_, refer to "Getting Started with Rails":../getting_started_with_rails.html +NOTE: For more information on Rails _scaffolding_, refer to "Getting Started with Rails":getting_started.html When you use +script/generate scaffold+, for a resource among other things it creates a test stub in the +test/unit+ folder: @@ -396,7 +396,7 @@ There are a bunch of different types of assertions you can use. Here's the compl |+assert_no_match( regexp, string, [msg] )+ |Ensures that a string doesn't matches the regular expression.| |+assert_in_delta( expecting, actual, delta, [msg] )+ |Ensures that the numbers +expecting+ and +actual+ are within +delta+ of each other.| |+assert_throws( symbol, [msg] ) { block }+ |Ensures that the given block throws the symbol.| -|+assert_raises( exception1, exception2, ... ) { block }+ |Ensures that the given block raises one of the given exceptions.| +|+assert_raise( exception1, exception2, ... ) { block }+ |Ensures that the given block raises one of the given exceptions.| |+assert_nothing_raised( exception1, exception2, ... ) { block }+ |Ensures that the given block doesn't raise one of the given exceptions.| |+assert_instance_of( class, obj, [msg] )+ |Ensures that +obj+ is of the +class+ type.| |+assert_kind_of( class, obj, [msg] )+ |Ensures that +obj+ is or descends from +class+.| @@ -429,7 +429,7 @@ h3. Functional Tests for Your Controllers In Rails, testing the various actions of a single controller is called writing functional tests for that controller. Controllers handle the incoming web requests to your application and eventually respond with a rendered view. -h4. What to include in your Functional Tests +h4. What to Include in your Functional Tests You should test for things such as: @@ -500,7 +500,7 @@ If you're familiar with the HTTP protocol, you'll know that +get+ is a type of r All of request types are methods that you can use, however, you'll probably end up using the first two more often than the others. -h4. The 4 Hashes of the Apocalypse +h4. The Four Hashes of the Apocalypse After a request has been made by using one of the 5 methods (+get+, +post+, etc.) and processed, you will have 4 Hash objects ready for use: @@ -582,9 +582,9 @@ assert_select "ol" do end -The +assert_select+ assertion is quite powerful. For more advanced usage, refer to its "documentation":http://api.rubyonrails.com/classes/ActionController/Assertions/SelectorAssertions.html. +The +assert_select+ assertion is quite powerful. For more advanced usage, refer to its "documentation":http://api.rubyonrails.org/classes/ActionController/Assertions/SelectorAssertions.html. -h5. Additional View-based Assertions +h5. Additional View-Based Assertions There are more assertions that are primarily used in testing views: @@ -631,7 +631,7 @@ end Integration tests inherit from +ActionController::IntegrationTest+. This makes available some additional helpers to use in your integration tests. Also you need to explicitly include the fixtures to be made available to the test. -h4. Helpers Available for Integration tests +h4. Helpers Available for Integration Tests In addition to the standard testing helpers, there are some additional helpers available to integration tests: @@ -741,7 +741,7 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai |+rake test:uncommitted+ |Runs all the tests which are uncommitted. Only supports Subversion| |+rake test:plugins+ |Run all the plugin tests from +vendor/plugins/*/**/test+ (or specify with +PLUGIN=_name_+)| -h3. Brief Note About Test::Unit +h3. Brief Note About +Test::Unit+ Ruby ships with a boat load of libraries. One little gem of a library is +Test::Unit+, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in +Test::Unit::Assertions+. The class +ActiveSupport::TestCase+ which we have been using in our unit and functional tests extends +Test::Unit::TestCase+ that it is how we can use all the basic assertions in our tests. @@ -872,7 +872,7 @@ For the purposes of unit testing a mailer, fixtures are used to provide an examp When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator you'll have to make those files yourself. -h5. The Basic Test case +h5. The Basic Test Case Here's a unit test to test a mailer named +UserMailer+ whose action +invite+ is used to send an invitation to a friend. It is an adapted version of the base test created by the generator for an +invite+ action. diff --git a/vendor/rails/railties/lib/commands/plugin.rb b/vendor/rails/railties/lib/commands/plugin.rb index 9ff47395..8589b169 100644 --- a/vendor/rails/railties/lib/commands/plugin.rb +++ b/vendor/rails/railties/lib/commands/plugin.rb @@ -134,7 +134,8 @@ class RailsEnvironment def externals return [] unless use_externals? ext = `svn propget svn:externals "#{root}/vendor/plugins"` - ext.reject{ |line| line.strip == '' }.map do |line| + lines = ext.respond_to?(:lines) ? ext.lines : ext + lines.reject{ |line| line.strip == '' }.map do |line| line.strip.split(/\s+/, 2) end end @@ -278,8 +279,8 @@ class Plugin base_cmd += " #{options[:revision]}" if options[:revision] puts base_cmd if $verbose if system(base_cmd) - puts "removing: .git" if $verbose - rm_rf ".git" + puts "removing: .git .gitignore" if $verbose + rm_rf %w(.git .gitignore) else rm_rf install_path end diff --git a/vendor/rails/railties/lib/initializer.rb b/vendor/rails/railties/lib/initializer.rb index edea4e51..a04405a7 100644 --- a/vendor/rails/railties/lib/initializer.rb +++ b/vendor/rails/railties/lib/initializer.rb @@ -301,7 +301,9 @@ module Rails end def load_gems - @configuration.gems.each { |gem| gem.load } + unless $gems_build_rake_task + @configuration.gems.each { |gem| gem.load } + end end def check_gem_dependencies diff --git a/vendor/rails/railties/lib/rails/backtrace_cleaner.rb b/vendor/rails/railties/lib/rails/backtrace_cleaner.rb index b6fdf42c..923ed8b3 100644 --- a/vendor/rails/railties/lib/rails/backtrace_cleaner.rb +++ b/vendor/rails/railties/lib/rails/backtrace_cleaner.rb @@ -4,15 +4,13 @@ module Rails RAILS_GEMS = %w( actionpack activerecord actionmailer activesupport activeresource rails ) - VENDOR_DIRS = %w( vendor/gems vendor/rails ) + VENDOR_DIRS = %w( vendor/rails ) SERVER_DIRS = %w( lib/mongrel bin/mongrel lib/passenger bin/passenger-spawn-server lib/rack ) RAILS_NOISE = %w( script/server ) RUBY_NOISE = %w( rubygems/custom_require benchmark.rb ) - GEMS_DIR = Gem.default_dir - ALL_NOISE = VENDOR_DIRS + SERVER_DIRS + RAILS_NOISE + RUBY_NOISE def initialize @@ -20,11 +18,25 @@ module Rails add_filter { |line| line.sub("#{RAILS_ROOT}/", '') } add_filter { |line| line.sub(ERB_METHOD_SIG, '') } add_filter { |line| line.sub('./', '/') } # for tests - add_filter { |line| line.sub(/(#{GEMS_DIR})\/gems\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) \4')} # http://gist.github.com/30430 + + add_gem_filters + add_silencer { |line| ALL_NOISE.any? { |dir| line.include?(dir) } } add_silencer { |line| RAILS_GEMS.any? { |gem| line =~ /^#{gem} / } } add_silencer { |line| line =~ %r(vendor/plugins/[^\/]+/lib) } end + + + private + def add_gem_filters + Gem.path.each do |path| + # http://gist.github.com/30430 + add_filter { |line| line.sub(/(#{path})\/gems\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) \4')} + end + + vendor_gems_path = Rails::GemDependency.unpacked_path.sub("#{RAILS_ROOT}/",'') + add_filter { |line| line.sub(/(#{vendor_gems_path})\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) [v] \4')} + end end # For installing the BacktraceCleaner in the test/unit diff --git a/vendor/rails/railties/lib/rails/gem_dependency.rb b/vendor/rails/railties/lib/rails/gem_dependency.rb index 2dd65903..3062a771 100644 --- a/vendor/rails/railties/lib/rails/gem_dependency.rb +++ b/vendor/rails/railties/lib/rails/gem_dependency.rb @@ -7,8 +7,8 @@ module Gem end module Rails - class GemDependency - attr_accessor :lib, :source + class GemDependency < Gem::Dependency + attr_accessor :lib, :source, :dep def self.unpacked_path @unpacked_path ||= File.join(RAILS_ROOT, 'vendor', 'gems') @@ -29,18 +29,6 @@ module Rails end end - def framework_gem? - @@framework_gems.has_key?(name) - end - - def vendor_rails? - Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty? - end - - def vendor_gem? - Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.include?(self.class.unpacked_path) - end - def initialize(name, options = {}) require 'rubygems' unless Object.const_defined?(:Gem) @@ -52,10 +40,11 @@ module Rails req = Gem::Requirement.default end - @dep = Gem::Dependency.new(name, req) @lib = options[:lib] @source = options[:source] @loaded = @frozen = @load_paths_added = false + + super(name, req) end def add_load_paths @@ -65,54 +54,76 @@ module Rails @load_paths_added = @loaded = @frozen = true return end - gem @dep + gem self @spec = Gem.loaded_specs[name] @frozen = @spec.loaded_from.include?(self.class.unpacked_path) if @spec @load_paths_added = true rescue Gem::LoadError end - def dependencies(options = {}) - return [] if framework_gem? || specification.nil? - - all_dependencies = specification.dependencies.map do |dependency| + def dependencies + return [] if framework_gem? + return [] unless installed? + specification.dependencies.reject do |dependency| + dependency.type == :development + end.map do |dependency| GemDependency.new(dependency.name, :requirement => dependency.version_requirements) end - - all_dependencies += all_dependencies.map { |d| d.dependencies(options) }.flatten if options[:flatten] - all_dependencies.uniq end - def gem_dir(base_directory) - File.join(base_directory, specification.full_name) - end - - def spec_filename(base_directory) - File.join(gem_dir(base_directory), '.specification') - end - - def load - return if @loaded || @load_paths_added == false - require(@lib || name) unless @lib == false - @loaded = true - rescue LoadError - puts $!.to_s - $!.backtrace.each { |b| puts b } - end - - def name - @dep.name.to_s + def specification + # code repeated from Gem.activate. Find a matching spec, or the currently loaded version. + # error out if loaded version and requested version are incompatible. + @spec ||= begin + matches = Gem.source_index.search(self) + matches << @@framework_gems[name] if framework_gem? + if Gem.loaded_specs[name] then + # This gem is already loaded. If the currently loaded gem is not in the + # list of candidate gems, then we have a version conflict. + existing_spec = Gem.loaded_specs[name] + unless matches.any? { |spec| spec.version == existing_spec.version } then + raise Gem::Exception, + "can't activate #{@dep}, already activated #{existing_spec.full_name}" + end + # we're stuck with it, so change to match + version_requirements = Gem::Requirement.create("=#{existing_spec.version}") + existing_spec + else + # new load + matches.last + end + end end def requirement - r = @dep.version_requirements + r = version_requirements (r == Gem::Requirement.default) ? nil : r end + def built? + # TODO: If Rubygems ever gives us a way to detect this, we should use it + false + end + + def framework_gem? + @@framework_gems.has_key?(name) + end + def frozen? @frozen ||= vendor_rails? || vendor_gem? end + def installed? + Gem.loaded_specs.keys.include?(name) + end + + def load_paths_added? + # always try to add load paths - even if a gem is loaded, it may not + # be a compatible version (ie random_gem 0.4 is loaded and a later spec + # needs >= 0.5 - gem 'random_gem' will catch this and error out) + @load_paths_added + end + def loaded? @loaded ||= begin if vendor_rails? @@ -136,48 +147,49 @@ module Rails end end - def load_paths_added? - # always try to add load paths - even if a gem is loaded, it may not - # be a compatible version (ie random_gem 0.4 is loaded and a later spec - # needs >= 0.5 - gem 'random_gem' will catch this and error out) - @load_paths_added + def vendor_rails? + Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty? + end + + def vendor_gem? + specification && File.exists?(unpacked_gem_directory) + end + + def build + require 'rails/gem_builder' + unless built? + return unless File.exists?(unpacked_specification_filename) + spec = YAML::load_file(unpacked_specification_filename) + Rails::GemBuilder.new(spec, unpacked_gem_directory).build_extensions + puts "Built gem: '#{unpacked_gem_directory}'" + end + dependencies.each { |dep| dep.build } end def install - cmd = "#{gem_command} #{install_command.join(' ')}" - puts cmd - puts %x(#{cmd}) - end - - def unpack_to(directory) - return if specification.nil? || File.directory?(gem_dir(directory)) || framework_gem? - - FileUtils.mkdir_p directory - Dir.chdir directory do - Gem::GemRunner.new.run(unpack_command) - end - - # Gem.activate changes the spec - get the original - real_spec = Gem::Specification.load(specification.loaded_from) - write_spec(directory, real_spec) - - end - - def write_spec(directory, spec) - # copy the gem's specification into GEMDIR/.specification so that - # we can access information about the gem on deployment systems - # without having the gem installed - File.open(spec_filename(directory), 'w') do |file| - file.puts spec.to_yaml + unless installed? + cmd = "#{gem_command} #{install_command.join(' ')}" + puts cmd + puts %x(#{cmd}) end end - def refresh_spec(directory) + def load + return if @loaded || @load_paths_added == false + require(@lib || name) unless @lib == false + @loaded = true + rescue LoadError + puts $!.to_s + $!.backtrace.each { |b| puts b } + end + + def refresh + Rails::VendorGemSourceIndex.silence_spec_warnings = true real_gems = Gem.source_index.installed_source_index exact_dep = Gem::Dependency.new(name, "= #{specification.version}") matches = real_gems.search(exact_dep) installed_spec = matches.first - if File.exist?(File.dirname(spec_filename(directory))) + if frozen? if installed_spec # we have a real copy # get a fresh spec - matches should only have one element @@ -185,11 +197,11 @@ module Rails # spec is the same as the copy from real_gems - Gem.activate changes # some of the fields real_spec = Gem::Specification.load(matches.first.loaded_from) - write_spec(directory, real_spec) + write_specification(real_spec) puts "Reloaded specification for #{name} from installed gems." else # the gem isn't installed locally - write out our current specs - write_spec(directory, specification) + write_specification(specification) puts "Gem #{name} not loaded locally - writing out current spec." end else @@ -201,42 +213,44 @@ module Rails end end + def unpack(options={}) + unless frozen? || framework_gem? + FileUtils.mkdir_p unpack_base + Dir.chdir unpack_base do + Gem::GemRunner.new.run(unpack_command) + end + # Gem.activate changes the spec - get the original + real_spec = Gem::Specification.load(specification.loaded_from) + write_specification(real_spec) + end + dependencies.each { |dep| dep.unpack } if options[:recursive] + end + + def write_specification(spec) + # copy the gem's specification into GEMDIR/.specification so that + # we can access information about the gem on deployment systems + # without having the gem installed + File.open(unpacked_specification_filename, 'w') do |file| + file.puts spec.to_yaml + end + end + def ==(other) self.name == other.name && self.requirement == other.requirement end alias_method :"eql?", :"==" - def hash - @dep.hash - end - - def specification - # code repeated from Gem.activate. Find a matching spec, or the currently loaded version. - # error out if loaded version and requested version are incompatible. - @spec ||= begin - matches = Gem.source_index.search(@dep) - matches << @@framework_gems[name] if framework_gem? - if Gem.loaded_specs[name] then - # This gem is already loaded. If the currently loaded gem is not in the - # list of candidate gems, then we have a version conflict. - existing_spec = Gem.loaded_specs[name] - unless matches.any? { |spec| spec.version == existing_spec.version } then - raise Gem::Exception, - "can't activate #{@dep}, already activated #{existing_spec.full_name}" - end - # we're stuck with it, so change to match - @dep.version_requirements = Gem::Requirement.create("=#{existing_spec.version}") - existing_spec - else - # new load - matches.last - end - end - end - private + def gem_command - RUBY_PLATFORM =~ /win32/ ? 'gem.bat' : 'gem' + case RUBY_PLATFORM + when /win32/ + 'gem.bat' + when /java/ + 'jruby -S gem' + else + 'gem' + end end def install_command @@ -251,5 +265,18 @@ module Rails cmd << "--version" << "= "+specification.version.to_s if requirement cmd end + + def unpack_base + Rails::GemDependency.unpacked_path + end + + def unpacked_gem_directory + File.join(unpack_base, specification.full_name) + end + + def unpacked_specification_filename + File.join(unpacked_gem_directory, '.specification') + end + end end diff --git a/vendor/rails/railties/lib/rails/rack/metal.rb b/vendor/rails/railties/lib/rails/rack/metal.rb index bce59f4c..adc43da8 100644 --- a/vendor/rails/railties/lib/rails/rack/metal.rb +++ b/vendor/rails/railties/lib/rails/rack/metal.rb @@ -15,9 +15,11 @@ module Rails metal_glob = metal_paths.map{ |base| "#{base}/**/*.rb" } all_metals = {} - Dir[*metal_glob].sort.map do |file| - file = file.match(matcher)[1] - all_metals[file.classify] = file + metal_glob.each do |glob| + Dir[glob].sort.map do |file| + file = file.match(matcher)[1] + all_metals[file.camelize] = file + end end load_list = requested_metals || all_metals.keys diff --git a/vendor/rails/railties/lib/rails/rack/static.rb b/vendor/rails/railties/lib/rails/rack/static.rb index ef4e2642..f07c6beb 100644 --- a/vendor/rails/railties/lib/rails/rack/static.rb +++ b/vendor/rails/railties/lib/rails/rack/static.rb @@ -13,14 +13,18 @@ module Rails def call(env) path = env['PATH_INFO'].chomp('/') method = env['REQUEST_METHOD'] - cached_path = (path.empty? ? 'index' : path) + ::ActionController::Base.page_cache_extension if FILE_METHODS.include?(method) if file_exist?(path) return @file_server.call(env) - elsif file_exist?(cached_path) - env['PATH_INFO'] = cached_path - return @file_server.call(env) + else + cached_path = directory_exist?(path) ? "#{path}/index" : path + cached_path += ::ActionController::Base.page_cache_extension + + if file_exist?(cached_path) + env['PATH_INFO'] = cached_path + return @file_server.call(env) + end end end @@ -32,6 +36,11 @@ module Rails full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path)) File.file?(full_path) && File.readable?(full_path) end + + def directory_exist?(path) + full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path)) + File.directory?(full_path) && File.readable?(full_path) + end end end end diff --git a/vendor/rails/railties/lib/rails/version.rb b/vendor/rails/railties/lib/rails/version.rb index fd38705e..99c7516a 100644 --- a/vendor/rails/railties/lib/rails/version.rb +++ b/vendor/rails/railties/lib/rails/version.rb @@ -2,7 +2,7 @@ module Rails module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/railties/lib/rails_generator/generators/applications/app/template_runner.rb b/vendor/rails/railties/lib/rails_generator/generators/applications/app/template_runner.rb index efed030d..3b49b1fa 100644 --- a/vendor/rails/railties/lib/rails_generator/generators/applications/app/template_runner.rb +++ b/vendor/rails/railties/lib/rails_generator/generators/applications/app/template_runner.rb @@ -90,7 +90,7 @@ module Rails gems_code = "config.gem '#{name}'" if options.any? - opts = options.inject([]) {|result, h| result << [":#{h[0]} => '#{h[1]}'"] }.sort.join(", ") + opts = options.inject([]) {|result, h| result << [":#{h[0]} => #{h[1].inspect.gsub('"',"'")}"] }.sort.join(", ") gems_code << ", #{opts}" end @@ -316,7 +316,7 @@ module Rails # def ask(string) log '', string - gets.strip + STDIN.gets.strip end # Do something in the root of the Rails application or diff --git a/vendor/rails/railties/lib/tasks/gems.rake b/vendor/rails/railties/lib/tasks/gems.rake index 0932ba73..ed07bf20 100644 --- a/vendor/rails/railties/lib/tasks/gems.rake +++ b/vendor/rails/railties/lib/tasks/gems.rake @@ -9,71 +9,57 @@ task :gems => 'gems:base' do puts "R = Framework (loaded before rails starts)" end -def print_gem_status(gem, indent=1) - code = gem.loaded? ? (gem.frozen? ? (gem.framework_gem? ? "R" : "F") : "I") : " " - puts " "*(indent-1)+" - [#{code}] #{gem.name} #{gem.requirement.to_s}" - gem.dependencies.each { |g| print_gem_status(g, indent+1)} if gem.loaded? -end - namespace :gems do task :base do $gems_rake_task = true + require 'rubygems' + require 'rubygems/gem_runner' Rake::Task[:environment].invoke end desc "Build any native extensions for unpacked gems" task :build do - $gems_rake_task = true - require 'rails/gem_builder' - Dir[File.join(Rails::GemDependency.unpacked_path, '*')].each do |gem_dir| - spec_file = File.join(gem_dir, '.specification') - next unless File.exists?(spec_file) - specification = YAML::load_file(spec_file) - next unless ENV['GEM'].blank? || ENV['GEM'] == specification.name - Rails::GemBuilder.new(specification, gem_dir).build_extensions - puts "Built gem: '#{gem_dir}'" - end + $gems_build_rake_task = true + Rake::Task['gems:unpack'].invoke + current_gems.each &:build end - desc "Installs all required gems for this application." + desc "Installs all required gems." task :install => :base do - require 'rubygems' - require 'rubygems/gem_runner' - Rails.configuration.gems.each { |gem| gem.install unless gem.loaded? } + current_gems.each &:install end - desc "Unpacks the specified gem into vendor/gems." - task :unpack => :base do - require 'rubygems' - require 'rubygems/gem_runner' - Rails.configuration.gems.each do |gem| - next unless ENV['GEM'].blank? || ENV['GEM'] == gem.name - gem.unpack_to(Rails::GemDependency.unpacked_path) - end + desc "Unpacks all required gems into vendor/gems." + task :unpack => :install do + current_gems.each &:unpack end namespace :unpack do - desc "Unpacks the specified gems and its dependencies into vendor/gems" - task :dependencies => :unpack do - require 'rubygems' - require 'rubygems/gem_runner' - Rails.configuration.gems.each do |gem| - next unless ENV['GEM'].blank? || ENV['GEM'] == gem.name - gem.dependencies(:flatten => true).each do |dependency| - dependency.unpack_to(Rails::GemDependency.unpacked_path) - end - end + desc "Unpacks all required gems and their dependencies into vendor/gems." + task :dependencies => :install do + current_gems.each { |gem| gem.unpack(:recursive => true) } end end desc "Regenerate gem specifications in correct format." task :refresh_specs => :base do - require 'rubygems' - require 'rubygems/gem_runner' - Rails::VendorGemSourceIndex.silence_spec_warnings = true - Rails.configuration.gems.each do |gem| - next unless gem.frozen? && (ENV['GEM'].blank? || ENV['GEM'] == gem.name) - gem.refresh_spec(Rails::GemDependency.unpacked_path) if gem.loaded? - end + current_gems.each &:refresh end -end \ No newline at end of file +end + +def current_gems + gems = Rails.configuration.gems + gems = gems.select { |gem| gem.name == ENV['GEM'] } unless ENV['GEM'].blank? + gems +end + +def print_gem_status(gem, indent=1) + code = case + when gem.framework_gem? then 'R' + when gem.frozen? then 'F' + when gem.installed? then 'I' + else ' ' + end + puts " "*(indent-1)+" - [#{code}] #{gem.name} #{gem.requirement.to_s}" + gem.dependencies.each { |g| print_gem_status(g, indent+1) } +end diff --git a/vendor/rails/railties/test/backtrace_cleaner_test.rb b/vendor/rails/railties/test/backtrace_cleaner_test.rb index bfb9cb76..7a1b3614 100644 --- a/vendor/rails/railties/test/backtrace_cleaner_test.rb +++ b/vendor/rails/railties/test/backtrace_cleaner_test.rb @@ -30,3 +30,32 @@ if defined? Test::Unit::Util::BacktraceFilter else $stderr.puts 'No BacktraceFilter for minitest' end + +class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase + def setup + @cleaner = Rails::BacktraceCleaner.new + end + + test "should format installed gems correctly" do + @backtrace = [ "#{Gem.default_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + @result = @cleaner.clean(@backtrace) + 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) + assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] + end + end + + test "should format vendor gems correctly" do + @backtrace = [ "#{Rails::GemDependency.unpacked_path}/nosuchgem-1.2.3/lib/foo.rb" ] + @result = @cleaner.clean(@backtrace) + assert_equal "nosuchgem (1.2.3) [v] lib/foo.rb", @result[0] + end + +end diff --git a/vendor/rails/railties/test/boot_test.rb b/vendor/rails/railties/test/boot_test.rb index 16776af0..08fcc82e 100644 --- a/vendor/rails/railties/test/boot_test.rb +++ b/vendor/rails/railties/test/boot_test.rb @@ -62,6 +62,8 @@ class VendorBootTest < Test::Unit::TestCase def test_load_initializer_requires_from_vendor_rails boot = VendorBoot.new boot.expects(:require).with("#{RAILS_ROOT}/vendor/rails/railties/lib/initializer") + Rails::Initializer.expects(:run).with(:install_gem_spec_stubs) + Rails::GemDependency.expects(:add_frozen_gem_path) boot.load_initializer end end diff --git a/vendor/rails/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb b/vendor/rails/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb index b8e70013..2d373ce4 100644 --- a/vendor/rails/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb +++ b/vendor/rails/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb @@ -1,5 +1,5 @@ class MetalA < Rails::Rack::Metal def self.call(env) - [200, { "Content-Type" => "text/html"}, "Hi"] + [200, { "Content-Type" => "text/html"}, ["Hi"]] end end diff --git a/vendor/rails/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb b/vendor/rails/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb index adc2f45f..a8bbf3fd 100644 --- a/vendor/rails/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb +++ b/vendor/rails/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb @@ -1,5 +1,5 @@ class MetalB < Rails::Rack::Metal def self.call(env) - [200, { "Content-Type" => "text/html"}, "Hi"] + [200, { "Content-Type" => "text/html"}, ["Hi"]] end end diff --git a/vendor/rails/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb b/vendor/rails/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb new file mode 100644 index 00000000..0cd3737c --- /dev/null +++ b/vendor/rails/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb @@ -0,0 +1,5 @@ +class LegacyRoutes < Rails::Rack::Metal + def self.call(env) + [301, { "Location" => "http://example.com"}, []] + end +end diff --git a/vendor/rails/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb b/vendor/rails/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb index 9ade2ce8..5f5b0875 100644 --- a/vendor/rails/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb +++ b/vendor/rails/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb @@ -1,5 +1,5 @@ class FooMetal < Rails::Rack::Metal def self.call(env) - [200, { "Content-Type" => "text/html"}, "Hi"] + [200, { "Content-Type" => "text/html"}, ["Hi"]] end end diff --git a/vendor/rails/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb b/vendor/rails/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb index 71a5a62e..25b3bb0a 100644 --- a/vendor/rails/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb +++ b/vendor/rails/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb @@ -1,7 +1,7 @@ module Folder class MetalA < Rails::Rack::Metal def self.call(env) - [200, { "Content-Type" => "text/html"}, "Hi"] + [200, { "Content-Type" => "text/html"}, ["Hi"]] end end end diff --git a/vendor/rails/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb b/vendor/rails/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb index 430d7bfe..7583363f 100644 --- a/vendor/rails/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb +++ b/vendor/rails/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb @@ -1,7 +1,7 @@ module Folder class MetalB < Rails::Rack::Metal def self.call(env) - [200, { "Content-Type" => "text/html"}, "Hi"] + [200, { "Content-Type" => "text/html"}, ["Hi"]] end end end diff --git a/vendor/rails/railties/test/fixtures/plugins/engines/engine/init.rb b/vendor/rails/railties/test/fixtures/plugins/engines/engine/init.rb index f4b00c0f..64e9ae6c 100644 --- a/vendor/rails/railties/test/fixtures/plugins/engines/engine/init.rb +++ b/vendor/rails/railties/test/fixtures/plugins/engines/engine/init.rb @@ -1,3 +1,3 @@ # My app/models dir must be in the load path. require 'engine_model' -raise 'missing model from my app/models dir' unless defined?(EngineModel) +raise LoadError, 'missing model from my app/models dir' unless defined?(EngineModel) diff --git a/vendor/rails/railties/test/fixtures/public/foo/bar.html b/vendor/rails/railties/test/fixtures/public/foo/bar.html new file mode 100644 index 00000000..9a356462 --- /dev/null +++ b/vendor/rails/railties/test/fixtures/public/foo/bar.html @@ -0,0 +1 @@ +/foo/bar.html \ No newline at end of file diff --git a/vendor/rails/railties/test/fixtures/public/foo/index.html b/vendor/rails/railties/test/fixtures/public/foo/index.html new file mode 100644 index 00000000..497a2e89 --- /dev/null +++ b/vendor/rails/railties/test/fixtures/public/foo/index.html @@ -0,0 +1 @@ +/foo/index.html \ No newline at end of file diff --git a/vendor/rails/railties/test/fixtures/public/index.html b/vendor/rails/railties/test/fixtures/public/index.html new file mode 100644 index 00000000..525950ba --- /dev/null +++ b/vendor/rails/railties/test/fixtures/public/index.html @@ -0,0 +1 @@ +/index.html \ No newline at end of file diff --git a/vendor/rails/railties/test/gem_dependency_test.rb b/vendor/rails/railties/test/gem_dependency_test.rb index 8b761c48..189ad02b 100644 --- a/vendor/rails/railties/test/gem_dependency_test.rb +++ b/vendor/rails/railties/test/gem_dependency_test.rb @@ -46,31 +46,34 @@ class GemDependencyTest < Test::Unit::TestCase end def test_gem_adds_load_paths - @gem.expects(:gem).with(Gem::Dependency.new(@gem.name, nil)) + @gem.expects(:gem).with(@gem) @gem.add_load_paths end def test_gem_with_version_adds_load_paths - @gem_with_version.expects(:gem).with(Gem::Dependency.new(@gem_with_version.name, @gem_with_version.requirement.to_s)) + @gem_with_version.expects(:gem).with(@gem_with_version) @gem_with_version.add_load_paths + assert @gem_with_version.load_paths_added? end def test_gem_loading - @gem.expects(:gem).with(Gem::Dependency.new(@gem.name, nil)) + @gem.expects(:gem).with(@gem) @gem.expects(:require).with(@gem.name) @gem.add_load_paths @gem.load + assert @gem.loaded? end def test_gem_with_lib_loading - @gem_with_lib.expects(:gem).with(Gem::Dependency.new(@gem_with_lib.name, nil)) + @gem_with_lib.expects(:gem).with(@gem_with_lib) @gem_with_lib.expects(:require).with(@gem_with_lib.lib) @gem_with_lib.add_load_paths @gem_with_lib.load + assert @gem_with_lib.loaded? end def test_gem_without_lib_loading - @gem_without_load.expects(:gem).with(Gem::Dependency.new(@gem_without_load.name, nil)) + @gem_without_load.expects(:gem).with(@gem_without_load) @gem_without_load.expects(:require).with(@gem_without_load.lib).never @gem_without_load.add_load_paths @gem_without_load.load @@ -132,8 +135,8 @@ class GemDependencyTest < Test::Unit::TestCase dummy_gem = Rails::GemDependency.new "dummy-gem-g" dummy_gem.add_load_paths dummy_gem.load - assert dummy_gem.loaded? - assert_equal 2, dummy_gem.dependencies(:flatten => true).size + assert_equal 1, dummy_gem.dependencies.size + assert_equal 1, dummy_gem.dependencies.first.dependencies.size assert_nothing_raised do dummy_gem.dependencies.each do |g| g.dependencies diff --git a/vendor/rails/railties/test/generators/rails_template_runner_test.rb b/vendor/rails/railties/test/generators/rails_template_runner_test.rb index 4e1937e0..2da6bd59 100644 --- a/vendor/rails/railties/test/generators/rails_template_runner_test.rb +++ b/vendor/rails/railties/test/generators/rails_template_runner_test.rb @@ -93,6 +93,11 @@ class RailsTemplateRunnerTest < GeneratorTestCase assert_generated_file_with_data('config/environments/test.rb', "config.gem 'quietbacktrace'") end + def test_gem_with_lib_option_set_to_false_should_put_gem_dependency_in_enviroment_correctly + run_template_method(:gem, 'mislav-will-paginate', :lib => false, :source => 'http://gems.github.com') + assert_rails_initializer_includes("config.gem 'mislav-will-paginate', :lib => false, :source => 'http://gems.github.com'") + end + def test_environment_should_include_data_in_environment_initializer_block load_paths = 'config.load_paths += %w["#{RAILS_ROOT}/app/extras"]' run_template_method(:environment, load_paths) diff --git a/vendor/rails/railties/test/initializer_test.rb b/vendor/rails/railties/test/initializer_test.rb index e91e05d9..561f7b8b 100644 --- a/vendor/rails/railties/test/initializer_test.rb +++ b/vendor/rails/railties/test/initializer_test.rb @@ -237,7 +237,7 @@ class InitializerPluginLoadingTests < Test::Unit::TestCase def test_registering_a_plugin_name_that_does_not_exist_raises_a_load_error only_load_the_following_plugins! [:stubby, :acts_as_a_non_existant_plugin] - assert_raises(LoadError) do + assert_raise(LoadError) do load_plugins! end end diff --git a/vendor/rails/railties/test/metal_test.rb b/vendor/rails/railties/test/metal_test.rb index a31f4ab2..d3d23113 100644 --- a/vendor/rails/railties/test/metal_test.rb +++ b/vendor/rails/railties/test/metal_test.rb @@ -8,6 +8,12 @@ class MetalTest < Test::Unit::TestCase end end + def test_metals_should_respect_class_name_conventions + use_appdir("pluralmetal") do + assert_equal(["LegacyRoutes"], found_metals_as_string_array) + end + end + def test_metals_should_return_alphabetical_list_of_found_metal_apps use_appdir("multiplemetals") do assert_equal(["MetalA", "MetalB"], found_metals_as_string_array) @@ -41,6 +47,15 @@ class MetalTest < Test::Unit::TestCase end end + def test_metal_finding_should_work_with_multiple_metal_paths_in_185_and_below + use_appdir("singlemetal") do + engine_metal_path = "#{File.dirname(__FILE__)}/fixtures/plugins/engines/engine/app/metal" + Rails::Rack::Metal.metal_paths << engine_metal_path + $LOAD_PATH << engine_metal_path + assert_equal(["FooMetal", "EngineMetal"], found_metals_as_string_array) + end + end + private def use_appdir(root) diff --git a/vendor/rails/railties/test/plugin_locator_test.rb b/vendor/rails/railties/test/plugin_locator_test.rb index c8404e12..471d9fc7 100644 --- a/vendor/rails/railties/test/plugin_locator_test.rb +++ b/vendor/rails/railties/test/plugin_locator_test.rb @@ -2,7 +2,7 @@ require 'plugin_test_helper' class PluginLocatorTest < Test::Unit::TestCase def test_should_require_subclasses_to_implement_the_plugins_method - assert_raises(RuntimeError) do + assert_raise(RuntimeError) do Rails::Plugin::Locator.new(nil).plugins end end diff --git a/vendor/rails/railties/test/plugin_test.rb b/vendor/rails/railties/test/plugin_test.rb index 5500711d..a6c390a4 100644 --- a/vendor/rails/railties/test/plugin_test.rb +++ b/vendor/rails/railties/test/plugin_test.rb @@ -52,11 +52,11 @@ class PluginTest < Test::Unit::TestCase plugin_for(@valid_plugin_path).load_paths end - assert_raises(LoadError) do + assert_raise(LoadError) do plugin_for(@empty_plugin_path).load_paths end - assert_raises(LoadError) do + assert_raise(LoadError) do plugin_for('this_is_not_a_plugin_directory').load_paths end end @@ -77,13 +77,13 @@ class PluginTest < Test::Unit::TestCase end # This is an empty path so it raises - assert_raises(LoadError) do + assert_raise(LoadError) do plugin = plugin_for(@empty_plugin_path) plugin.stubs(:evaluate_init_rb) plugin.send(:load, @initializer) end - assert_raises(LoadError) do + assert_raise(LoadError) do plugin = plugin_for('this_is_not_a_plugin_directory') plugin.stubs(:evaluate_init_rb) plugin.send(:load, @initializer) @@ -97,11 +97,11 @@ class PluginTest < Test::Unit::TestCase end # This is an empty path so it raises - assert_raises(LoadError) do + assert_raise(LoadError) do plugin_for(@empty_plugin_path).load_paths end - assert_raises(LoadError) do + assert_raise(LoadError) do plugin_for('this_is_not_a_plugin_directory').load_paths end end diff --git a/vendor/rails/railties/test/rack_static_test.rb b/vendor/rails/railties/test/rack_static_test.rb new file mode 100644 index 00000000..ad673f6f --- /dev/null +++ b/vendor/rails/railties/test/rack_static_test.rb @@ -0,0 +1,46 @@ +require 'abstract_unit' + +require 'action_controller' +require 'rails/rack' + +class RackStaticTest < ActiveSupport::TestCase + def setup + FileUtils.cp_r "#{RAILS_ROOT}/fixtures/public", "#{RAILS_ROOT}/public" + end + + def teardown + FileUtils.rm_rf "#{RAILS_ROOT}/public" + end + + DummyApp = lambda { |env| + [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]] + } + App = Rails::Rack::Static.new(DummyApp) + + test "serves dynamic content" do + assert_equal "Hello, World!", get("/nofile") + end + + test "serves static index at root" do + assert_equal "/index.html", get("/index.html") + assert_equal "/index.html", get("/index") + assert_equal "/index.html", get("/") + end + + test "serves static file in directory" do + assert_equal "/foo/bar.html", get("/foo/bar.html") + assert_equal "/foo/bar.html", get("/foo/bar/") + assert_equal "/foo/bar.html", get("/foo/bar") + end + + test "serves static index file in directory" do + assert_equal "/foo/index.html", get("/foo/index.html") + assert_equal "/foo/index.html", get("/foo/") + assert_equal "/foo/index.html", get("/foo") + end + + private + def get(path) + Rack::MockRequest.new(App).request("GET", path).body + end +end diff --git a/vendor/rails/railties/test/vendor/gems/dummy-gem-g-1.0.0/.specification b/vendor/rails/railties/test/vendor/gems/dummy-gem-g-1.0.0/.specification index 5483048c..27e29912 100644 --- a/vendor/rails/railties/test/vendor/gems/dummy-gem-g-1.0.0/.specification +++ b/vendor/rails/railties/test/vendor/gems/dummy-gem-g-1.0.0/.specification @@ -9,7 +9,7 @@ date: 2008-10-03 00:00:00 -04:00 dependencies: - !ruby/object:Gem::Dependency name: dummy-gem-f - type: :development + type: :runtime version_requirement: version_requirements: !ruby/object:Gem::Requirement requirements: