From 4bdf703ab2801a1f2f824dcc7508249512fd99fd Mon Sep 17 00:00:00 2001 From: Jacques Distler Date: Sat, 5 Sep 2009 02:01:46 -0500 Subject: [PATCH] Instiki 0.17.2: Security Release This release upgrades Instiki to Rails 2.3.4, which patches two security holes in Rails. See http://weblog.rubyonrails.org/2009/9/4/ruby-on-rails-2-3-4 There are also some new features, and the usual boatload of bugfixes. See the CHANGELOG for details. --- CHANGELOG | 31 + app/controllers/application_controller.rb | 2 +- vendor/rails/actionmailer/CHANGELOG | 4 + vendor/rails/actionmailer/Rakefile | 2 +- .../actionmailer/lib/action_mailer/base.rb | 1 + .../actionmailer/lib/action_mailer/version.rb | 2 +- .../actionmailer/test/mail_service_test.rb | 5 +- vendor/rails/actionpack/CHANGELOG | 11 + vendor/rails/actionpack/Rakefile | 4 +- vendor/rails/actionpack/examples/minimal.rb | 87 + .../actionpack/examples/views/_collection.erb | 1 + .../actionpack/examples/views/_hello.erb | 1 + .../examples/views/_many_partials.erb | 10 + .../actionpack/examples/views/_partial.erb | 10 + .../examples/views/layouts/alt.html.erb | 1 + .../examples/views/layouts/kaigi.html.erb | 1 + .../examples/views/template.html.erb | 1 + .../rails/actionpack/lib/action_controller.rb | 8 +- .../assertions/response_assertions.rb | 4 +- .../actionpack/lib/action_controller/base.rb | 8 +- .../lib/action_controller/cookies.rb | 2 +- .../lib/action_controller/dispatcher.rb | 27 +- .../action_controller/http_authentication.rb | 5 +- .../lib/action_controller/params_parser.rb | 6 + .../lib/action_controller/reloader.rb | 51 +- .../request_forgery_protection.rb | 3 +- .../lib/action_controller/resources.rb | 30 +- .../lib/action_controller/response.rb | 6 + .../lib/action_controller/routing.rb | 3 + .../action_controller/routing/route_set.rb | 23 +- .../lib/action_controller/streaming.rb | 4 +- .../lib/action_controller/url_rewriter.rb | 2 +- .../actionpack/lib/action_pack/version.rb | 2 +- .../action_view/helpers/atom_feed_helper.rb | 2 +- .../lib/action_view/helpers/form_helper.rb | 5 +- .../helpers/form_options_helper.rb | 70 +- .../lib/action_view/helpers/tag_helper.rb | 2 +- .../lib/action_view/helpers/text_helper.rb | 31 +- .../lib/action_view/helpers/url_helper.rb | 2 +- .../actionpack/lib/action_view/locale/en.yml | 4 + vendor/rails/actionpack/test/abstract_unit.rb | 16 + .../test/controller/caching_test.rb | 2 +- .../actionpack/test/controller/cookie_test.rb | 6 + .../test/controller/dispatcher_test.rb | 61 +- .../test/controller/filter_params_test.rb | 3 +- .../http_basic_authentication_test.rb | 25 + .../http_digest_authentication_test.rb | 35 +- .../actionpack/test/controller/rack_test.rb | 19 +- .../test/controller/redirect_test.rb | 2 +- .../test/controller/reloader_test.rb | 69 +- .../request/json_params_parsing_test.rb | 30 +- .../request/xml_params_parsing_test.rb | 15 + .../request_forgery_protection_test.rb | 11 +- .../test/controller/resources_test.rb | 44 + .../test/controller/routing_test.rb | 9 +- .../test/controller/send_file_test.rb | 12 +- .../test/controller/url_rewriter_test.rb | 32 +- .../test/fixtures/public/absolute/test.css | 23 + .../test/fixtures/public/absolute/test.js | 63 + .../test/template/atom_feed_helper_test.rb | 29 + .../test/template/form_helper_test.rb | 26 + .../template/form_options_helper_i18n_test.rb | 27 + .../test/template/form_options_helper_test.rb | 34 + .../test/template/text_helper_test.rb | 23 + .../test/template/url_helper_test.rb | 8 + vendor/rails/activerecord/CHANGELOG | 7 + vendor/rails/activerecord/Rakefile | 32 +- vendor/rails/activerecord/examples/.gitignore | 1 + .../activerecord/examples/performance.rb | 162 ++ .../lib/active_record/associations.rb | 42 +- .../associations/association_collection.rb | 1 + .../has_and_belongs_to_many_association.rb | 16 + .../associations/has_many_association.rb | 1 + .../has_many_through_association.rb | 16 +- .../has_one_through_association.rb | 10 +- .../lib/active_record/autosave_association.rb | 7 +- .../activerecord/lib/active_record/base.rb | 28 +- .../lib/active_record/calculations.rb | 2 + .../abstract/schema_definitions.rb | 18 +- .../abstract/schema_statements.rb | 4 +- .../connection_adapters/abstract_adapter.rb | 7 + .../connection_adapters/mysql_adapter.rb | 25 +- .../connection_adapters/postgresql_adapter.rb | 45 +- .../connection_adapters/sqlite_adapter.rb | 12 + .../activerecord/lib/active_record/dirty.rb | 2 +- .../lib/active_record/fixtures.rb | 16 +- .../i18n_interpolation_deprecation.rb | 2 +- .../lib/active_record/locale/en.yml | 4 + .../lib/active_record/named_scope.rb | 7 +- .../lib/active_record/reflection.rb | 2 +- .../lib/active_record/schema_dumper.rb | 3 +- .../serializers/json_serializer.rb | 10 +- .../serializers/xml_serializer.rb | 8 +- .../lib/active_record/validations.rb | 227 ++- .../activerecord/lib/active_record/version.rb | 2 +- .../activerecord/test/cases/adapter_test.rb | 12 + .../belongs_to_associations_test.rb | 18 - .../eager_load_nested_include_test.rb | 10 +- .../associations/habtm_join_table_test.rb | 56 + .../has_many_associations_test.rb | 58 +- .../has_many_through_associations_test.rb | 47 +- .../has_one_through_associations_test.rb | 10 + .../cases/associations/join_model_test.rb | 8 +- .../activerecord/test/cases/base_test.rb | 53 +- .../test/cases/calculations_test.rb | 6 + .../test/cases/column_definition_test.rb | 34 + .../activerecord/test/cases/dirty_test.rb | 10 + .../activerecord/test/cases/finder_test.rb | 65 +- .../activerecord/test/cases/fixtures_test.rb | 2 +- .../activerecord/test/cases/i18n_test.rb | 5 + .../test/cases/method_scoping_test.rb | 2 +- .../activerecord/test/cases/migration_test.rb | 50 +- .../activerecord/test/cases/modules_test.rb | 42 + .../test/cases/named_scope_test.rb | 10 +- .../rails/activerecord/test/cases/pk_test.rb | 18 + .../test/cases/reflection_test.rb | 4 +- .../test/cases/schema_dumper_test.rb | 20 +- .../test/cases/validations_i18n_test.rb | 1514 +++++++++-------- .../test/cases/validations_test.rb | 14 +- .../test/cases/xml_serialization_test.rb | 20 + .../activerecord/test/fixtures/posts.yml | 3 + .../rails/activerecord/test/models/author.rb | 1 + .../rails/activerecord/test/models/comment.rb | 6 +- .../rails/activerecord/test/models/company.rb | 2 + .../test/models/company_in_module.rb | 2 +- .../activerecord/test/models/contract.rb | 5 + .../activerecord/test/models/organization.rb | 2 + .../rails/activerecord/test/models/topic.rb | 2 - .../test/schema/postgresql_specific_schema.rb | 15 +- .../rails/activerecord/test/schema/schema.rb | 4 + vendor/rails/activeresource/CHANGELOG | 12 + vendor/rails/activeresource/Rakefile | 2 +- .../lib/active_resource/base.rb | 80 +- .../lib/active_resource/connection.rb | 81 +- .../lib/active_resource/exceptions.rb | 66 + .../lib/active_resource/validations.rb | 24 +- .../lib/active_resource/version.rb | 2 +- .../activeresource/test/base/load_test.rb | 17 +- .../activeresource/test/base_errors_test.rb | 75 +- vendor/rails/activeresource/test/base_test.rb | 147 ++ .../activeresource/test/connection_test.rb | 42 + .../activeresource/test/fixtures/proxy.rb | 4 + vendor/rails/activesupport/CHANGELOG | 6 + vendor/rails/activesupport/Rakefile | 5 +- .../activesupport/lib/active_support/all.rb | 8 + .../core_ext/array/conversions.rb | 1 + .../core_ext/date/calculations.rb | 1 + .../lib/active_support/core_ext/enumerable.rb | 6 +- .../core_ext/hash/conversions.rb | 1 + .../core_ext/module/delegation.rb | 13 +- .../lib/active_support/core_ext/string.rb | 1 + .../core_ext/string/bytesize.rb | 5 + .../core_ext/time/calculations.rb | 14 +- .../lib/active_support/deprecation.rb | 18 +- .../lib/active_support/json/backends/yaml.rb | 7 +- .../lib/active_support/memoizable.rb | 2 +- .../lib/active_support/message_verifier.rb | 19 +- .../lib/active_support/multibyte.rb | 36 +- .../lib/active_support/multibyte/chars.rb | 40 +- .../lib/active_support/multibyte/utils.rb | 61 + .../lib/active_support/test_case.rb | 3 +- .../lib/active_support/version.rb | 2 +- .../test/core_ext/array_ext_test.rb | 7 + .../test/core_ext/date_ext_test.rb | 8 +- .../test/core_ext/enumerable_test.rb | 4 + .../test/core_ext/hash_ext_test.rb | 7 + .../test/core_ext/module_test.rb | 13 +- .../test/core_ext/string_ext_test.rb | 7 + .../test/core_ext/time_ext_test.rb | 46 +- .../activesupport/test/deprecation_test.rb | 4 + ...flush_cache_on_private_memoization_test.rb | 44 + .../activesupport/test/json/decoding_test.rb | 10 +- .../test/multibyte_chars_test.rb | 12 + .../test/multibyte_utils_test.rb | 141 ++ vendor/rails/ci/cruise_config.rb | 11 +- vendor/rails/ci/geminstaller.yml | 2 +- vendor/rails/railties/CHANGELOG | 6 + vendor/rails/railties/Rakefile | 13 +- vendor/rails/railties/bin/about | 4 +- vendor/rails/railties/bin/console | 2 +- vendor/rails/railties/bin/dbconsole | 2 +- vendor/rails/railties/bin/destroy | 2 +- vendor/rails/railties/bin/generate | 2 +- .../railties/bin/performance/benchmarker | 2 +- .../rails/railties/bin/performance/profiler | 2 +- vendor/rails/railties/bin/plugin | 2 +- vendor/rails/railties/bin/runner | 2 +- vendor/rails/railties/bin/server | 2 +- .../railties/builtin/rails_info/rails/info.rb | 6 +- .../initializers/new_rails_defaults.rb | 2 + vendor/rails/railties/configs/seeds.rb | 7 + vendor/rails/railties/environments/boot.rb | 2 +- .../rails/railties/lib/commands/dbconsole.rb | 12 +- vendor/rails/railties/lib/initializer.rb | 2 +- vendor/rails/railties/lib/rails/plugin.rb | 12 + .../rails/railties/lib/rails/plugin/loader.rb | 7 + vendor/rails/railties/lib/rails/version.rb | 2 +- .../railties/lib/rails_generator/base.rb | 2 +- .../applications/app/app_generator.rb | 5 + .../generators/applications/app/scm/git.rb | 10 +- .../applications/app/template_runner.rb | 2 +- .../components/model/model_generator.rb | 11 +- .../components/scaffold/scaffold_generator.rb | 1 + .../rails/railties/lib/tasks/databases.rake | 21 +- vendor/rails/railties/lib/tasks/routes.rake | 7 +- .../engines/engine/config/locales/en.yml | 2 + .../test/generators/generator_test_helper.rb | 7 + .../generators/rails_model_generator_test.rb | 48 + .../rails_scaffold_generator_test.rb | 1 + .../rails/railties/test/initializer_test.rb | 2 + .../rails/railties/test/plugin_loader_test.rb | 8 + 211 files changed, 3959 insertions(+), 1325 deletions(-) create mode 100644 vendor/rails/actionpack/examples/minimal.rb create mode 100644 vendor/rails/actionpack/examples/views/_collection.erb create mode 100644 vendor/rails/actionpack/examples/views/_hello.erb create mode 100644 vendor/rails/actionpack/examples/views/_many_partials.erb create mode 100644 vendor/rails/actionpack/examples/views/_partial.erb create mode 100644 vendor/rails/actionpack/examples/views/layouts/alt.html.erb create mode 100644 vendor/rails/actionpack/examples/views/layouts/kaigi.html.erb create mode 100644 vendor/rails/actionpack/examples/views/template.html.erb create mode 100644 vendor/rails/actionpack/test/fixtures/public/absolute/test.css create mode 100644 vendor/rails/actionpack/test/fixtures/public/absolute/test.js create mode 100644 vendor/rails/actionpack/test/template/form_options_helper_i18n_test.rb create mode 100644 vendor/rails/activerecord/examples/.gitignore create mode 100755 vendor/rails/activerecord/examples/performance.rb create mode 100644 vendor/rails/activerecord/test/cases/associations/habtm_join_table_test.rb create mode 100644 vendor/rails/activerecord/test/models/contract.rb create mode 100644 vendor/rails/activeresource/lib/active_resource/exceptions.rb create mode 100644 vendor/rails/activeresource/test/fixtures/proxy.rb create mode 100644 vendor/rails/activesupport/lib/active_support/all.rb create mode 100644 vendor/rails/activesupport/lib/active_support/core_ext/string/bytesize.rb create mode 100644 vendor/rails/activesupport/lib/active_support/multibyte/utils.rb create mode 100644 vendor/rails/activesupport/test/flush_cache_on_private_memoization_test.rb create mode 100644 vendor/rails/activesupport/test/multibyte_utils_test.rb create mode 100644 vendor/rails/railties/configs/seeds.rb create mode 100644 vendor/rails/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml diff --git a/CHANGELOG b/CHANGELOG index 8ddd619a..36216dc0 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,34 @@ +* 0.17.2 + +Security: Updated to Rails 2.3.4 +* Fixes Timing Weakness in Rails MessageVerifier and the Cookie Store + http://weblog.rubyonrails.org/2009/9/4/timing-weakness-in-ruby-on-rails +* Fixes XSS Vulnerability in Rails + http://weblog.rubyonrails.org/2009/9/4/xss-vulnerability-in-ruby-on-rails + +New Features: +* Syntax colouring (`ruby` and `html`) for code blocks. +* Updated for itex2MML 1.3.10 (supports \rlap{} and \underline{}). You should upgrade that, too. +* Add a "Create New Page" Link to the Search Page. (Based on an idea by nowa) +* Updated to Rails 2.3.4 + +Bugs Fixed: +* Wikilinks to published webs should be to the published action. This didn't work + right for inter-web links. (Reported by Mike Shulman) +* Use .size, rather than .length for ActiveRecord associations. A huge memory saving + in building the recently_revised page. +* Refactor the upgrade_instiki rake task, to make it database-agnostic. (Many thanks to James Herdman) +* Web#files_path and Web#blatex_pngs_path now return Pathname objects. (Thanks, again, to James Herdman) +* Workaround for Mozilla Bug 449396. (Reported by Andrew Stacey) +* Correctly Set noindex,nofollow On /diff Pages. +* Page-renaming javascript deals correctly with page names containing ampersands, slashes, and other garbage. +* List of Wanted Pages should not include redirected pages. +* The Regexp, used in Maruku to detect "email" headers (used, e.g., for S5 slideshow metadata) could, for some inputs, interact badly with Instiki's Chunk Handler. Fixed. +* Ensure "rollback" locks page for editing. +* Generate relative URLs, when possible. (Patch by Dennis Knauf) +* Expire revisions of an edited page. Use a `before_save` hook to deal with the situation where a page's name has been changed. + +------------------------------------------------------------------------------ * 0.17 New features: diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c464969e..d8bab69b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -258,7 +258,7 @@ module Instiki module VERSION #:nodoc: MAJOR = 0 MINOR = 17 - TINY = 0 + TINY = 2 SUFFIX = '(MML+)' PRERELEASE = false if PRERELEASE diff --git a/vendor/rails/actionmailer/CHANGELOG b/vendor/rails/actionmailer/CHANGELOG index 4090dd81..22f30226 100644 --- a/vendor/rails/actionmailer/CHANGELOG +++ b/vendor/rails/actionmailer/CHANGELOG @@ -1,3 +1,7 @@ +*2.3.4 (September 4, 2009)* + +* Minor bug fixes. + *2.3.3 (July 12, 2009)* * No changes, just a version bump. diff --git a/vendor/rails/actionmailer/Rakefile b/vendor/rails/actionmailer/Rakefile index e6dc2591..6dcb7f43 100644 --- a/vendor/rails/actionmailer/Rakefile +++ b/vendor/rails/actionmailer/Rakefile @@ -54,7 +54,7 @@ spec = Gem::Specification.new do |s| s.rubyforge_project = "actionmailer" s.homepage = "http://www.rubyonrails.org" - s.add_dependency('actionpack', '= 2.3.3' + PKG_BUILD) + s.add_dependency('actionpack', '= 2.3.4' + 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 000c05d1..84997de3 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/base.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/base.rb @@ -543,6 +543,7 @@ module ActionMailer #:nodoc: @headers ||= {} @body ||= {} @mime_version = @@default_mime_version.dup if @@default_mime_version + @sent_on ||= Time.now end def render_message(method_name, body) diff --git a/vendor/rails/actionmailer/lib/action_mailer/version.rb b/vendor/rails/actionmailer/lib/action_mailer/version.rb index db86a297..855b0ef6 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 = 3 + TINY = 4 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/actionmailer/test/mail_service_test.rb b/vendor/rails/actionmailer/test/mail_service_test.rb index 277a9139..3244aad2 100644 --- a/vendor/rails/actionmailer/test/mail_service_test.rb +++ b/vendor/rails/actionmailer/test/mail_service_test.rb @@ -18,7 +18,6 @@ class TestMailer < ActionMailer::Base @recipients = recipient @subject = "[Signed up] Welcome #{recipient}" @from = "system@loudthinking.com" - @sent_on = Time.local(2004, 12, 12) @body["recipient"] = recipient end @@ -356,12 +355,14 @@ class ActionMailerTest < Test::Unit::TestCase end def test_signed_up + Time.stubs(:now => Time.now) + expected = new_mail expected.to = @recipient expected.subject = "[Signed up] Welcome #{@recipient}" expected.body = "Hello there, \n\nMr. #{@recipient}" expected.from = "system@loudthinking.com" - expected.date = Time.local(2004, 12, 12) + expected.date = Time.now created = nil assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) } diff --git a/vendor/rails/actionpack/CHANGELOG b/vendor/rails/actionpack/CHANGELOG index 5f6fec67..94894c08 100644 --- a/vendor/rails/actionpack/CHANGELOG +++ b/vendor/rails/actionpack/CHANGELOG @@ -1,3 +1,12 @@ +*2.3.4 (September 4, 2009)* + +* Sanitize multibyte strings before escaping them with escape_once. CVE-2009-3009 + +* Introduce grouped_collection_select helper. #1249 [Dan Codeape, Erik Ostrom] + +* Ruby 1.9: fix Content-Length for multibyte send_data streaming. #2661 [Sava Chankov] + + *2.3.3 (July 12, 2009)* * Fixed that TestResponse.cookies was returning cookies unescaped #1867 [Doug McInnes] @@ -7,6 +16,8 @@ * Fixed that redirection would just log the options, not the final url (which lead to "Redirected to #") [DHH] +* Don't check authenticity tokens for any AJAX requests [Ross Kaffenberger/Bryan Helmkamp] + * Added ability to pass in :public => true to fresh_when, stale?, and expires_in to make the request proxy cachable #2095 [Gregg Pollack] * Fixed that passing a custom form builder would be forwarded to nested fields_for calls #2023 [Eloy Duran/Nate Wiger] diff --git a/vendor/rails/actionpack/Rakefile b/vendor/rails/actionpack/Rakefile index 17f6f1ac..ade3f227 100644 --- a/vendor/rails/actionpack/Rakefile +++ b/vendor/rails/actionpack/Rakefile @@ -29,7 +29,7 @@ Rake::TestTask.new(:test_action_pack) do |t| # make sure we include the tests in alphabetical order as on some systems # this will not happen automatically and the tests (as a whole) will error - t.test_files = Dir.glob( "test/[cft]*/**/*_test.rb" ).sort + t.test_files = Dir.glob( "test/[cftv]*/**/*_test.rb" ).sort t.verbose = true #t.warning = true @@ -79,7 +79,7 @@ spec = Gem::Specification.new do |s| s.has_rdoc = true s.requirements << 'none' - s.add_dependency('activesupport', '= 2.3.3' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.4' + PKG_BUILD) s.add_dependency('rack', '~> 1.0.0') s.require_path = 'lib' diff --git a/vendor/rails/actionpack/examples/minimal.rb b/vendor/rails/actionpack/examples/minimal.rb new file mode 100644 index 00000000..c7774542 --- /dev/null +++ b/vendor/rails/actionpack/examples/minimal.rb @@ -0,0 +1,87 @@ +$:.push File.join(File.dirname(__FILE__), "..", "lib") +$:.push File.join(File.dirname(__FILE__), "..", "..", "activesupport", "lib") +require "action_controller" + +class Runner + def initialize(app, output) + @app, @output = app, output + end + + def puts(*) + super if @output + end + + def call(env) + env['n'].to_i.times { @app.call(env) } + @app.call(env).tap { |response| report(env, response) } + end + + def report(env, response) + if ENV["DEBUG"] + out = env['rack.errors'] + p response.headers + out.puts response.status, response.headers.to_yaml, '---' + response.body.each { |part| out.puts part } + out.puts '---' + end + end + + def self.puts(*) + super if @output + end + + def self.run(app, n, label = nil, uri = "/", output = true) + @output = output + puts label, '=' * label.size if label + env = Rack::MockRequest.env_for(uri).merge('n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout) + t = Benchmark.realtime { new(app, output).call(env) } + puts "%d ms / %d req = %.1f usec/req" % [10**3 * t, n, 10**6 * t / n] + puts + end +end + +N = (ENV['N'] || 1000).to_i + +class BasePostController < ActionController::Base + append_view_path "#{File.dirname(__FILE__)}/views" + + def index + render :text => 'Hello' + end + + def partial + render :partial => "/partial" + end + + def many_partials + render :partial => "/many_partials" + end + + def partial_collection + render :partial => "/collection", :collection => [1,2,3,4,5,6,7,8,9,10] + end + + def show_template + render :template => "template" + end +end + +# p BasePostController.call(Rack::MockRequest.env_for("/?action=index").merge("REQUEST_URI" => "/")).body + +Runner.run(BasePostController, N, 'index', "/?action=index", false) +Runner.run(BasePostController, N, 'partial', "/?action=partial", false) +Runner.run(BasePostController, N, 'many partials', "/?action=many_partials", false) +Runner.run(BasePostController, N, 'collection', "/?action=partial_collection", false) +Runner.run(BasePostController, N, 'template', "/?action=show_template", false) + +(ENV["M"] || 1).to_i.times do + Runner.run(BasePostController, N, 'index', "/?action=index") + Runner.run(BasePostController, N, 'partial', "/?action=partial") + Runner.run(BasePostController, N, 'many partials', "/?action=many_partials") + Runner.run(BasePostController, N, 'collection', "/?action=partial_collection") + Runner.run(BasePostController, N, 'template', "/?action=show_template") +end + # Runner.run(BasePostController.action(:many_partials), N, 'index') + # Runner.run(BasePostController.action(:many_partials), N, 'many_partials') + # Runner.run(BasePostController.action(:partial_collection), N, 'collection') + # Runner.run(BasePostController.action(:show_template), N, 'template') diff --git a/vendor/rails/actionpack/examples/views/_collection.erb b/vendor/rails/actionpack/examples/views/_collection.erb new file mode 100644 index 00000000..bcfe958e --- /dev/null +++ b/vendor/rails/actionpack/examples/views/_collection.erb @@ -0,0 +1 @@ +<%= collection %> \ No newline at end of file diff --git a/vendor/rails/actionpack/examples/views/_hello.erb b/vendor/rails/actionpack/examples/views/_hello.erb new file mode 100644 index 00000000..5ab2f8a4 --- /dev/null +++ b/vendor/rails/actionpack/examples/views/_hello.erb @@ -0,0 +1 @@ +Hello \ No newline at end of file diff --git a/vendor/rails/actionpack/examples/views/_many_partials.erb b/vendor/rails/actionpack/examples/views/_many_partials.erb new file mode 100644 index 00000000..7e379d46 --- /dev/null +++ b/vendor/rails/actionpack/examples/views/_many_partials.erb @@ -0,0 +1,10 @@ +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> \ No newline at end of file diff --git a/vendor/rails/actionpack/examples/views/_partial.erb b/vendor/rails/actionpack/examples/views/_partial.erb new file mode 100644 index 00000000..3ca8e80b --- /dev/null +++ b/vendor/rails/actionpack/examples/views/_partial.erb @@ -0,0 +1,10 @@ +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> diff --git a/vendor/rails/actionpack/examples/views/layouts/alt.html.erb b/vendor/rails/actionpack/examples/views/layouts/alt.html.erb new file mode 100644 index 00000000..c4816337 --- /dev/null +++ b/vendor/rails/actionpack/examples/views/layouts/alt.html.erb @@ -0,0 +1 @@ ++ <%= yield %> + \ No newline at end of file diff --git a/vendor/rails/actionpack/examples/views/layouts/kaigi.html.erb b/vendor/rails/actionpack/examples/views/layouts/kaigi.html.erb new file mode 100644 index 00000000..274607a9 --- /dev/null +++ b/vendor/rails/actionpack/examples/views/layouts/kaigi.html.erb @@ -0,0 +1 @@ +Hello <%= yield %> Goodbye \ No newline at end of file diff --git a/vendor/rails/actionpack/examples/views/template.html.erb b/vendor/rails/actionpack/examples/views/template.html.erb new file mode 100644 index 00000000..5ab2f8a4 --- /dev/null +++ b/vendor/rails/actionpack/examples/views/template.html.erb @@ -0,0 +1 @@ +Hello \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_controller.rb b/vendor/rails/actionpack/lib/action_controller.rb index 187c958c..f53ba277 100644 --- a/vendor/rails/actionpack/lib/action_controller.rb +++ b/vendor/rails/actionpack/lib/action_controller.rb @@ -31,12 +31,8 @@ rescue LoadError end end -begin - gem 'rack', '~> 1.1.0' - require 'rack' -rescue Gem::LoadError - require 'action_controller/vendor/rack-1.1.pre/rack' -end +gem 'rack', '~> 1.0.0' +require 'rack' module ActionController # TODO: Review explicit to see if they will automatically be handled by diff --git a/vendor/rails/actionpack/lib/action_controller/assertions/response_assertions.rb b/vendor/rails/actionpack/lib/action_controller/assertions/response_assertions.rb index 989be2db..931a0319 100644 --- a/vendor/rails/actionpack/lib/action_controller/assertions/response_assertions.rb +++ b/vendor/rails/actionpack/lib/action_controller/assertions/response_assertions.rb @@ -64,7 +64,9 @@ module ActionController # Support partial arguments for hash redirections if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash) if options.all? {|(key, value)| @response.redirected_to[key] == value} - ::ActiveSupport::Deprecation.warn("Using assert_redirected_to with partial hash arguments is deprecated. Specify the full set arguments instead", caller) + callstack = caller.dup + callstack.slice!(0, 2) + ::ActiveSupport::Deprecation.warn("Using assert_redirected_to with partial hash arguments is deprecated. Specify the full set arguments instead", callstack) return true end end diff --git a/vendor/rails/actionpack/lib/action_controller/base.rb b/vendor/rails/actionpack/lib/action_controller/base.rb index 3c89fc8b..1d8cc14a 100644 --- a/vendor/rails/actionpack/lib/action_controller/base.rb +++ b/vendor/rails/actionpack/lib/action_controller/base.rb @@ -493,7 +493,12 @@ module ActionController #:nodoc: filtered_parameters[key] = filter_parameters(value) elsif value.is_a?(Array) filtered_parameters[key] = value.collect do |item| - filter_parameters(item) + case item + when Hash, Array + filter_parameters(item) + else + item + end end elsif block_given? key = key.dup @@ -814,7 +819,6 @@ module ActionController #:nodoc: # render :text => proc { |response, output| # 10_000_000.times do |i| # output.write("This is line #{i}\n") - # output.flush # end # } # diff --git a/vendor/rails/actionpack/lib/action_controller/cookies.rb b/vendor/rails/actionpack/lib/action_controller/cookies.rb index ca380e98..d4806623 100644 --- a/vendor/rails/actionpack/lib/action_controller/cookies.rb +++ b/vendor/rails/actionpack/lib/action_controller/cookies.rb @@ -51,7 +51,7 @@ module ActionController #:nodoc: protected # Returns the cookie container, which operates as described above. def cookies - CookieJar.new(self) + @cookies ||= CookieJar.new(self) end end diff --git a/vendor/rails/actionpack/lib/action_controller/dispatcher.rb b/vendor/rails/actionpack/lib/action_controller/dispatcher.rb index 07931e4a..1932944f 100644 --- a/vendor/rails/actionpack/lib/action_controller/dispatcher.rb +++ b/vendor/rails/actionpack/lib/action_controller/dispatcher.rb @@ -2,13 +2,12 @@ module ActionController # Dispatches requests to the appropriate controller and takes care of # reloading the app after each request when Dependencies.load? is true. class Dispatcher + @@cache_classes = true + class << self def define_dispatcher_callbacks(cache_classes) + @@cache_classes = cache_classes unless cache_classes - unless self.middleware.include?(Reloader) - self.middleware.insert_after(Failsafe, Reloader) - end - ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false end @@ -79,7 +78,7 @@ module ActionController # DEPRECATE: Remove arguments, since they are only used by CGI def initialize(output = $stdout, request = nil, response = nil) @output = output - @app = @@middleware.build(lambda { |env| self.dup._call(env) }) + build_middleware_stack if @@cache_classes end def dispatch @@ -103,7 +102,18 @@ module ActionController end def call(env) - @app.call(env) + if @@cache_classes + @app.call(env) + else + Reloader.run do + # When class reloading is turned on, we will want to rebuild the + # middleware stack every time we process a request. If we don't + # rebuild the middleware stack, then the stack may contain references + # to old classes metal classes, which will b0rk class reloading. + build_middleware_stack + @app.call(env) + end + end end def _call(env) @@ -114,5 +124,10 @@ module ActionController def flush_logger Base.logger.flush end + + private + def build_middleware_stack + @app = @@middleware.build(lambda { |env| self.dup._call(env) }) + 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 88c289bc..ac872231 100644 --- a/vendor/rails/actionpack/lib/action_controller/http_authentication.rb +++ b/vendor/rails/actionpack/lib/action_controller/http_authentication.rb @@ -139,7 +139,7 @@ module ActionController end def decode_credentials(request) - ActiveSupport::Base64.decode64(authorization(request).split.last || '') + ActiveSupport::Base64.decode64(authorization(request).split(' ', 2).last || '') end def encode_credentials(user_name, password) @@ -195,9 +195,10 @@ module ActionController return false unless password method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] + uri = credentials[:uri][0,1] == '/' ? request.request_uri : request.url [true, false].any? do |password_is_ha1| - expected = expected_response(method, request.env['REQUEST_URI'], credentials, password, password_is_ha1) + expected = expected_response(method, uri, credentials, password, password_is_ha1) expected == credentials[:response] end end diff --git a/vendor/rails/actionpack/lib/action_controller/params_parser.rb b/vendor/rails/actionpack/lib/action_controller/params_parser.rb index d269fe07..f7e3c54b 100644 --- a/vendor/rails/actionpack/lib/action_controller/params_parser.rb +++ b/vendor/rails/actionpack/lib/action_controller/params_parser.rb @@ -47,6 +47,8 @@ module ActionController false end rescue Exception => e # YAML, XML or Ruby code block errors + logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}" + raise { "body" => request.raw_post, "content_type" => request.content_type, @@ -67,5 +69,9 @@ module ActionController nil end + + def logger + defined?(Rails.logger) ? Rails.logger : Logger.new($stderr) + end end end diff --git a/vendor/rails/actionpack/lib/action_controller/reloader.rb b/vendor/rails/actionpack/lib/action_controller/reloader.rb index 459dece1..709d88a3 100644 --- a/vendor/rails/actionpack/lib/action_controller/reloader.rb +++ b/vendor/rails/actionpack/lib/action_controller/reloader.rb @@ -1,14 +1,21 @@ +require 'thread' + module ActionController class Reloader + @@default_lock = Mutex.new + cattr_accessor :default_lock + class BodyWrapper - def initialize(body) + def initialize(body, lock) @body = body + @lock = lock end def close @body.close if @body.respond_to?(:close) ensure Dispatcher.cleanup_application + @lock.unlock end def method_missing(*args, &block) @@ -20,26 +27,28 @@ module ActionController end end - def initialize(app) - @app = app - end - - def call(env) - Dispatcher.reload_application - status, headers, body = @app.call(env) - # We do not want to call 'cleanup_application' in an ensure block - # because the returned Rack response body may lazily generate its data. This - # is for example the case if one calls - # - # render :text => lambda { ... code here which refers to application models ... } - # - # in an ActionController. - # - # Instead, we will want to cleanup the application code after the request is - # completely finished. So we wrap the body in a BodyWrapper class so that - # when the Rack handler calls #close during the end of the request, we get to - # run our cleanup code. - [status, headers, BodyWrapper.new(body)] + def self.run(lock = @@default_lock) + lock.lock + begin + Dispatcher.reload_application + status, headers, body = yield + # We do not want to call 'cleanup_application' in an ensure block + # because the returned Rack response body may lazily generate its data. This + # is for example the case if one calls + # + # render :text => lambda { ... code here which refers to application models ... } + # + # in an ActionController. + # + # Instead, we will want to cleanup the application code after the request is + # completely finished. So we wrap the body in a BodyWrapper class so that + # when the Rack handler calls #close during the end of the request, we get to + # run our cleanup code. + [status, headers, BodyWrapper.new(body, lock)] + rescue Exception + lock.unlock + raise + end end end end diff --git a/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb b/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb index f3e6288c..3067122c 100644 --- a/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb +++ b/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb @@ -81,12 +81,13 @@ module ActionController #:nodoc: # Returns true or false if a request is verified. Checks: # - # * is the format restricted? By default, only HTML and AJAX requests are checked. + # * is the format restricted? By default, only HTML requests are checked. # * is it a GET request? Gets should be safe and idempotent # * Does the form_authenticity_token match the given token value from the params? def verified_request? !protect_against_forgery? || request.method == :get || + request.xhr? || !verifiable_request_format? || form_authenticity_token == params[request_forgery_protection_token] end diff --git a/vendor/rails/actionpack/lib/action_controller/resources.rb b/vendor/rails/actionpack/lib/action_controller/resources.rb index 86abb7b2..8c2080e5 100644 --- a/vendor/rails/actionpack/lib/action_controller/resources.rb +++ b/vendor/rails/actionpack/lib/action_controller/resources.rb @@ -317,9 +317,10 @@ module ActionController # notes.resources :attachments # end # - # * :path_names - Specify different names for the 'new' and 'edit' actions. For example: + # * :path_names - Specify different path names for the actions. For example: # # new_products_path == '/productos/nuevo' - # map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' } + # # bids_product_path(1) == '/productos/1/licitacoes' + # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' } # # You can also set default action names from an environment, like this: # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' } @@ -525,16 +526,16 @@ module ActionController resource = Resource.new(entities, options) with_options :controller => resource.controller do |map| - map_collection_actions(map, resource) - map_default_collection_actions(map, resource) - map_new_actions(map, resource) - map_member_actions(map, resource) - map_associations(resource, options) if block_given? with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) end + + map_collection_actions(map, resource) + map_default_collection_actions(map, resource) + map_new_actions(map, resource) + map_member_actions(map, resource) end end @@ -542,16 +543,16 @@ module ActionController resource = SingletonResource.new(entities, options) with_options :controller => resource.controller do |map| - map_collection_actions(map, resource) - map_new_actions(map, resource) - map_member_actions(map, resource) - map_default_singleton_actions(map, resource) - map_associations(resource, options) if block_given? with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) end + + map_collection_actions(map, resource) + map_new_actions(map, resource) + map_member_actions(map, resource) + map_default_singleton_actions(map, resource) end end @@ -586,7 +587,10 @@ module ActionController resource.collection_methods.each do |method, actions| actions.each do |action| [method].flatten.each do |m| - map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) + action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) + action_path ||= action + + map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) end end end diff --git a/vendor/rails/actionpack/lib/action_controller/response.rb b/vendor/rails/actionpack/lib/action_controller/response.rb index def03fac..afee6a4c 100644 --- a/vendor/rails/actionpack/lib/action_controller/response.rb +++ b/vendor/rails/actionpack/lib/action_controller/response.rb @@ -166,6 +166,12 @@ module ActionController # :nodoc: str end + def flush #:nodoc: + ActiveSupport::Deprecation.warn( + 'Calling output.flush is no longer needed for streaming output ' + + 'because ActionController::Response automatically handles it', caller) + end + def set_cookie(key, value) if value.has_key?(:http_only) ActiveSupport::Deprecation.warn( diff --git a/vendor/rails/actionpack/lib/action_controller/routing.rb b/vendor/rails/actionpack/lib/action_controller/routing.rb index c0eb6134..f9b0c4bb 100644 --- a/vendor/rails/actionpack/lib/action_controller/routing.rb +++ b/vendor/rails/actionpack/lib/action_controller/routing.rb @@ -271,6 +271,9 @@ module ActionController ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set + mattr_accessor :generate_best_match + self.generate_best_match = true + # The root paths which may contain controller files mattr_accessor :controller_paths self.controller_paths = [] diff --git a/vendor/rails/actionpack/lib/action_controller/routing/route_set.rb b/vendor/rails/actionpack/lib/action_controller/routing/route_set.rb index a983d376..8e4ed7bc 100644 --- a/vendor/rails/actionpack/lib/action_controller/routing/route_set.rb +++ b/vendor/rails/actionpack/lib/action_controller/routing/route_set.rb @@ -405,11 +405,14 @@ module ActionController end # don't use the recalled keys when determining which routes to check - routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }] + future_routes, deprecated_routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }] + routes = Routing.generate_best_match ? deprecated_routes : future_routes - routes.each do |route| + routes.each_with_index do |route, index| results = route.__send__(method, options, merged, expire_on) - return results if results && (!results.is_a?(Array) || results.first) + if results && (!results.is_a?(Array) || results.first) + return results + end end end @@ -448,7 +451,10 @@ module ActionController @routes_by_controller ||= Hash.new do |controller_hash, controller| controller_hash[controller] = Hash.new do |action_hash, action| action_hash[action] = Hash.new do |key_hash, keys| - key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys) + key_hash[keys] = [ + routes_for_controller_and_action_and_keys(controller, action, keys), + deprecated_routes_for_controller_and_action_and_keys(controller, action, keys) + ] end end end @@ -460,10 +466,11 @@ module ActionController merged = options if expire_on[:controller] action = merged[:action] || 'index' - routes_by_controller[controller][action][merged.keys] + routes_by_controller[controller][action][merged.keys][1] end def routes_for_controller_and_action(controller, action) + ActiveSupport::Deprecation.warn "routes_for_controller_and_action() has been deprecated. Please use routes_for()" selected = routes.select do |route| route.matches_controller_and_action? controller, action end @@ -471,6 +478,12 @@ module ActionController end def routes_for_controller_and_action_and_keys(controller, action, keys) + routes.select do |route| + route.matches_controller_and_action? controller, action + end + end + + def deprecated_routes_for_controller_and_action_and_keys(controller, action, keys) selected = routes.select do |route| route.matches_controller_and_action? controller, action end diff --git a/vendor/rails/actionpack/lib/action_controller/streaming.rb b/vendor/rails/actionpack/lib/action_controller/streaming.rb index 8a9fbfc1..372470f0 100644 --- a/vendor/rails/actionpack/lib/action_controller/streaming.rb +++ b/vendor/rails/actionpack/lib/action_controller/streaming.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/string/bytesize' + module ActionController #:nodoc: # Methods for sending arbitrary data and for streaming files to the browser, # instead of rendering. @@ -137,7 +139,7 @@ module ActionController #:nodoc: # instead. See ActionController::Base#render for more information. def send_data(data, options = {}) #:doc: logger.info "Sending data #{options[:filename]}" if logger - send_file_headers! options.merge(:length => data.size) + send_file_headers! options.merge(:length => data.bytesize) @performed_render = false render :status => options[:status], :text => data end diff --git a/vendor/rails/actionpack/lib/action_controller/url_rewriter.rb b/vendor/rails/actionpack/lib/action_controller/url_rewriter.rb index bb6cb437..e4c2a29e 100644 --- a/vendor/rails/actionpack/lib/action_controller/url_rewriter.rb +++ b/vendor/rails/actionpack/lib/action_controller/url_rewriter.rb @@ -184,7 +184,7 @@ module ActionController path = rewrite_path(options) rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) - rewritten_url << "##{options[:anchor]}" if options[:anchor] + rewritten_url << "##{CGI.escape(options[:anchor].to_param.to_s)}" if options[:anchor] rewritten_url end diff --git a/vendor/rails/actionpack/lib/action_pack/version.rb b/vendor/rails/actionpack/lib/action_pack/version.rb index 66fca7df..698330a9 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 = 3 + TINY = 4 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/atom_feed_helper.rb index dc449758..9951e11a 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -98,7 +98,7 @@ module ActionView options[:schema_date] = "2005" # The Atom spec copyright date end - xml = options[:xml] || eval("xml", block.binding) + xml = options.delete(:xml) || eval("xml", block.binding) xml.instruct! if options[:instruct] options[:instruct].each do |target,attrs| diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb index 2ac407c7..db7de8ee 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb @@ -726,6 +726,7 @@ module ActionView options = options.stringify_keys tag_value = options.delete("value") name_and_id = options.dup + name_and_id["id"] = name_and_id["for"] add_default_name_and_id_for_value(tag_value, name_and_id) options.delete("index") options["for"] ||= name_and_id["id"] @@ -860,8 +861,8 @@ module ActionView private def add_default_name_and_id_for_value(tag_value, options) - if tag_value - pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase + unless tag_value.nil? + pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase specified_id = options["id"] add_default_name_and_id(options) options["id"] += "_#{pretty_tag_value}" unless specified_id diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb index 6adbab17..ec0e3d6b 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -162,6 +162,60 @@ module ActionView InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options) end + + # Returns + # + # + # + # + # + # + # + # + # + # + def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) + InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) + end + + + # Return select and option tags for the given object and method, using # #time_zone_options_for_select to generate the list of option tags. # @@ -490,6 +544,15 @@ module ActionView ) end + def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) + html_options = html_options.stringify_keys + add_default_name_and_id(html_options) + value = value(object) + content_tag( + "select", add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options + ) + end + def to_time_zone_select_tag(priority_zones, options, html_options) html_options = html_options.stringify_keys add_default_name_and_id(html_options) @@ -508,7 +571,8 @@ module ActionView option_tags = "\n" + option_tags end if value.blank? && options[:prompt] - ("\n") + option_tags + prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('support.select.prompt', :default => 'Please select') + "\n" + option_tags else option_tags end @@ -524,6 +588,10 @@ module ActionView @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) end + def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) + @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options)) + end + def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options)) end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/tag_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/tag_helper.rb index af8c4d5e..db99a0ef 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/tag_helper.rb @@ -103,7 +103,7 @@ module ActionView # escape_once("<< Accept & Checkout") # # => "<< Accept & Checkout" def escape_once(html) - html.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] } + ActiveSupport::Multibyte.clean(html.to_s).gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] } end private 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 8463af97..de6a2dd7 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/text_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/text_helper.rb @@ -33,30 +33,31 @@ module ActionView end # Truncates a given +text+ after a given :length if +text+ is longer than :length - # (defaults to 30). The last characters will be replaced with the :omission (defaults to "..."). + # (defaults to 30). The last characters will be replaced with the :omission (defaults to "...") + # for a total length not exceeding :length. # # ==== Examples # # truncate("Once upon a time in a world far far away") - # # => Once upon a time in a world f... + # # => Once upon a time in a world... # # truncate("Once upon a time in a world far far away", :length => 14) # # => Once upon a... # # truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)") - # # => And they found that many (clipped) + # # => And they found t(clipped) # - # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15) - # # => And they found... (continued) + # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 25) + # # => And they f... (continued) # # You can still use truncate with the old API that accepts the # +length+ as its optional second and the +ellipsis+ as its # optional third parameter: # truncate("Once upon a time in a world far far away", 14) - # # => Once upon a time in a world f... + # # => Once upon a... # - # truncate("And they found that many people were sleeping better.", 15, "... (continued)") - # # => And they found... (continued) + # truncate("And they found that many people were sleeping better.", 25, "... (continued)") + # # => And they f... (continued) def truncate(text, *args) options = args.extract_options! unless args.empty? @@ -234,12 +235,20 @@ module ActionView # # textilize("Visit the Rails website "here":http://www.rubyonrails.org/.) # # => "

Visit the Rails website here.

" - def textilize(text) + # + # textilize("This is worded strongly") + # # => "

This is worded strongly

" + # + # textilize("This is worded strongly", :filter_html) + # # => "

This is worded <strong>strongly</strong>

" + # + def textilize(text, *options) + options ||= [:hard_breaks] + if text.blank? "" else - textilized = RedCloth.new(text, [ :hard_breaks ]) - textilized.hard_breaks = true if textilized.respond_to?(:hard_breaks=) + textilized = RedCloth.new(text, options) textilized.to_html end end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/url_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/url_helper.rb index 92708861..f692af12 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/url_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/url_helper.rb @@ -568,7 +568,7 @@ module ActionView when confirm && popup "if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;" when confirm && method - "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method)} };return false;" + "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method, url, href)} };return false;" when confirm "return #{confirm_javascript_function(confirm)};" when method diff --git a/vendor/rails/actionpack/lib/action_view/locale/en.yml b/vendor/rails/actionpack/lib/action_view/locale/en.yml index afe35691..c82cd07e 100644 --- a/vendor/rails/actionpack/lib/action_view/locale/en.yml +++ b/vendor/rails/actionpack/lib/action_view/locale/en.yml @@ -108,3 +108,7 @@ # The variable :count is also available body: "There were problems with the following fields:" + support: + select: + # default value for :prompt => true in FormOptionsHelper + prompt: "Please select" \ No newline at end of file diff --git a/vendor/rails/actionpack/test/abstract_unit.rb b/vendor/rails/actionpack/test/abstract_unit.rb index 0b67e56d..8acc4513 100644 --- a/vendor/rails/actionpack/test/abstract_unit.rb +++ b/vendor/rails/actionpack/test/abstract_unit.rb @@ -43,3 +43,19 @@ ActionController::Base.view_paths = FIXTURE_LOAD_PATH CACHED_VIEW_PATHS = ActionView::Base.cache_template_loading? ? ActionController::Base.view_paths : ActionController::Base.view_paths.map {|path| ActionView::Template::EagerPath.new(path.to_s)} + +class DummyMutex + def lock + @locked = true + end + + def unlock + @locked = false + end + + def locked? + @locked + end +end + +ActionController::Reloader.default_lock = DummyMutex.new \ No newline at end of file diff --git a/vendor/rails/actionpack/test/controller/caching_test.rb b/vendor/rails/actionpack/test/controller/caching_test.rb index 6dcb67e5..223f5f70 100644 --- a/vendor/rails/actionpack/test/controller/caching_test.rb +++ b/vendor/rails/actionpack/test/controller/caching_test.rb @@ -52,7 +52,7 @@ class PageCachingTest < ActionController::TestCase ActionController::Base.perform_caching = true ActionController::Routing::Routes.draw do |map| - map.main '', :controller => 'posts' + map.main '', :controller => 'posts', :format => nil map.formatted_posts 'posts.:format', :controller => 'posts' map.resources :posts map.connect ':controller/:action/:id' diff --git a/vendor/rails/actionpack/test/controller/cookie_test.rb b/vendor/rails/actionpack/test/controller/cookie_test.rb index f7d97e16..ac47536c 100644 --- a/vendor/rails/actionpack/test/controller/cookie_test.rb +++ b/vendor/rails/actionpack/test/controller/cookie_test.rb @@ -118,4 +118,10 @@ class CookieTest < ActionController::TestCase get :delete_cookie_with_path assert_equal ["user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"], @response.headers["Set-Cookie"] end + + def test_cookies_persist_throughout_request + get :authenticate + cookies = @controller.send(:cookies) + assert_equal 'david', cookies['user_name'] + end end diff --git a/vendor/rails/actionpack/test/controller/dispatcher_test.rb b/vendor/rails/actionpack/test/controller/dispatcher_test.rb index 3aa5bf98..61e70070 100644 --- a/vendor/rails/actionpack/test/controller/dispatcher_test.rb +++ b/vendor/rails/actionpack/test/controller/dispatcher_test.rb @@ -2,25 +2,17 @@ require 'abstract_unit' class DispatcherTest < Test::Unit::TestCase Dispatcher = ActionController::Dispatcher + Reloader = ActionController::Reloader def setup ENV['REQUEST_METHOD'] = 'GET' - - Dispatcher.middleware = ActionController::MiddlewareStack.new do |middleware| - middlewares = File.expand_path(File.join(File.dirname(__FILE__), "../../lib/action_controller/middlewares.rb")) - middleware.instance_eval(File.read(middlewares)) - end - - # Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks - Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new) - Dispatcher.instance_variable_set("@before_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new) - Dispatcher.instance_variable_set("@after_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new) - + reset_dispatcher Dispatcher.stubs(:require_dependency) end def teardown ENV.delete 'REQUEST_METHOD' + reset_dispatcher end def test_clears_dependencies_after_dispatch_if_in_loading_mode @@ -41,6 +33,34 @@ class DispatcherTest < Test::Unit::TestCase dispatch end + def test_builds_middleware_stack_only_during_initialization_if_not_in_loading_mode + dispatcher = create_dispatcher + assert_not_nil dispatcher.instance_variable_get(:"@app") + dispatcher.instance_variable_set(:"@app", lambda { |env| }) + dispatcher.expects(:build_middleware_stack).never + dispatcher.call(nil) + dispatcher.call(nil) + end + + def test_rebuilds_middleware_stack_on_every_request_if_in_loading_mode + dispatcher = create_dispatcher(false) + dispatcher.instance_variable_set(:"@app", lambda { |env| }) + dispatcher.expects(:build_middleware_stack).twice + dispatcher.call(nil) + Reloader.default_lock.unlock + dispatcher.call(nil) + end + + def test_doesnt_wrap_call_in_reloader_if_not_in_loading_mode + Reloader.expects(:run).never + dispatch + end + + def test_wraps_call_in_reloader_if_in_loading_mode + Reloader.expects(:run).once + dispatch(false) + end + # Stub out dispatch error logger class << Dispatcher def log_failsafe_exception(status, exception); end @@ -99,6 +119,25 @@ class DispatcherTest < Test::Unit::TestCase Dispatcher.new.call({'rack.input' => StringIO.new('')}) end + def create_dispatcher(cache_classes = true) + Dispatcher.define_dispatcher_callbacks(cache_classes) + Dispatcher.new + end + + def reset_dispatcher + Dispatcher.middleware = ActionController::MiddlewareStack.new do |middleware| + middlewares = File.expand_path(File.join(File.dirname(__FILE__), "../../lib/action_controller/middlewares.rb")) + middleware.instance_eval(File.read(middlewares)) + end + + # Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks + Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + Dispatcher.instance_variable_set("@before_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + Dispatcher.instance_variable_set("@after_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + + Dispatcher.define_dispatcher_callbacks(true) + end + def assert_subclasses(howmany, klass, message = klass.subclasses.inspect) assert_equal howmany, klass.subclasses.size, message end diff --git a/vendor/rails/actionpack/test/controller/filter_params_test.rb b/vendor/rails/actionpack/test/controller/filter_params_test.rb index 0f480881..6994aaed 100644 --- a/vendor/rails/actionpack/test/controller/filter_params_test.rb +++ b/vendor/rails/actionpack/test/controller/filter_params_test.rb @@ -24,7 +24,8 @@ class FilterParamTest < Test::Unit::TestCase [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'], [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'], [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'], - [{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, %w(foo)]] + [{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, %w(foo)], + [{'baz'=>[{'foo'=>'baz'}, 1, 2, 3]}, {'baz'=>[{'foo'=>'[FILTERED]'}, 1, 2, 3]}, %w(foo)]] test_hashes.each do |before_filter, after_filter, filter_words| FilterParamController.filter_parameter_logging(*filter_words) diff --git a/vendor/rails/actionpack/test/controller/http_basic_authentication_test.rb b/vendor/rails/actionpack/test/controller/http_basic_authentication_test.rb index fbc94a0d..23688ca5 100644 --- a/vendor/rails/actionpack/test/controller/http_basic_authentication_test.rb +++ b/vendor/rails/actionpack/test/controller/http_basic_authentication_test.rb @@ -4,6 +4,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase class DummyController < ActionController::Base before_filter :authenticate, :only => :index before_filter :authenticate_with_request, :only => :display + before_filter :authenticate_long_credentials, :only => :show def index render :text => "Hello Secret" @@ -12,6 +13,10 @@ class HttpBasicAuthenticationTest < ActionController::TestCase def display render :text => 'Definitely Maybe' end + + def show + render :text => 'Only for loooooong credentials' + end private @@ -28,6 +33,12 @@ class HttpBasicAuthenticationTest < ActionController::TestCase request_http_basic_authentication("SuperSecret") end end + + def authenticate_long_credentials + authenticate_or_request_with_http_basic do |username, password| + username == '1234567890123456789012345678901234567890' && password == '1234567890123456789012345678901234567890' + end + end end AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'] @@ -42,6 +53,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase assert_response :success assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}" end + test "successful authentication with #{header.downcase} and long credentials" do + @request.env[header] = encode_credentials('1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890') + get :show + + assert_response :success + assert_equal 'Only for loooooong credentials', @response.body, "Authentication failed for request header #{header} and long credentials" + end end AUTH_HEADERS.each do |header| @@ -52,6 +70,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase assert_response :unauthorized assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}" end + test "unsuccessful authentication with #{header.downcase} and long credentials" do + @request.env[header] = encode_credentials('h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r', 'worldworldworldworldworldworldworldworld') + get :show + + assert_response :unauthorized + assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials" + end end test "authentication request without credential" do 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 5cc47ee7..6f6215e6 100644 --- a/vendor/rails/actionpack/test/controller/http_digest_authentication_test.rb +++ b/vendor/rails/actionpack/test/controller/http_digest_authentication_test.rb @@ -129,7 +129,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase assert_equal 'Definitely Maybe', @response.body end - test "authentication request with request-uri that doesn't match credentials digest-uri" do + 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 @@ -138,10 +138,33 @@ class HttpDigestAuthenticationTest < ActionController::TestCase 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", + test "authentication request with absolute request uri (as in webrick)" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest" + + get :display + + assert_response :success + assert assigns(:logged_in) + assert_equal 'Definitely Maybe', @response.body + end + + test "authentication request with absolute uri in credentials (as in IE)" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest", :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 absolute uri in both request and credentials (as in Webrick with IE)" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest", + :username => 'pretty', :password => 'please') + @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest" + get :display assert_response :success @@ -199,11 +222,11 @@ class HttpDigestAuthenticationTest < ActionController::TestCase credentials = decode_credentials(@response.headers['WWW-Authenticate']) credentials.merge!(options) - credentials.reverse_merge!(:uri => "#{@request.env['REQUEST_URI']}") + credentials.merge!(:uri => @request.env['REQUEST_URI'].to_s) ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1]) end def decode_credentials(header) ActionController::HttpAuthentication::Digest.decode_credentials(@response.headers['WWW-Authenticate']) end -end \ No newline at end of file +end diff --git a/vendor/rails/actionpack/test/controller/rack_test.rb b/vendor/rails/actionpack/test/controller/rack_test.rb index b550d3db..aeace2b8 100644 --- a/vendor/rails/actionpack/test/controller/rack_test.rb +++ b/vendor/rails/actionpack/test/controller/rack_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' -class BaseRackTest < Test::Unit::TestCase +class BaseRackTest < ActiveSupport::TestCase def setup @env = { "HTTP_MAX_FORWARDS" => "10", @@ -261,6 +261,23 @@ class RackResponseTest < BaseRackTest body.each { |part| parts << part } assert_equal ["0", "1", "2", "3", "4"], parts end + + def test_streaming_block_with_flush_is_deprecated + @response.body = Proc.new do |response, output| + 5.times do |n| + output.write(n) + output.flush + end + end + + assert_deprecated(/output.flush is no longer needed/) do + @response.prepare! + status, headers, body = @response.to_a + + parts = [] + body.each { |part| parts << part } + end + end end class RackResponseHeadersTest < BaseRackTest diff --git a/vendor/rails/actionpack/test/controller/redirect_test.rb b/vendor/rails/actionpack/test/controller/redirect_test.rb index c4574869..e80502b6 100644 --- a/vendor/rails/actionpack/test/controller/redirect_test.rb +++ b/vendor/rails/actionpack/test/controller/redirect_test.rb @@ -236,7 +236,7 @@ class RedirectTest < ActionController::TestCase def test_redirect_with_partial_params get :module_redirect - assert_deprecated do + assert_deprecated(/test_redirect_with_partial_params/) do assert_redirected_to :action => 'hello_world' end end diff --git a/vendor/rails/actionpack/test/controller/reloader_test.rb b/vendor/rails/actionpack/test/controller/reloader_test.rb index 6f1f3c85..e5305493 100644 --- a/vendor/rails/actionpack/test/controller/reloader_test.rb +++ b/vendor/rails/actionpack/test/controller/reloader_test.rb @@ -22,35 +22,62 @@ class ReloaderTests < ActiveSupport::TestCase end end - def setup_and_return_body(app = lambda { }) - Dispatcher.expects(:reload_application) - reloader = Reloader.new(app) - headers, status, body = reloader.call({ }) - body + def setup + @lock = Mutex.new end - def test_it_reloads_the_application_before_the_request + def test_it_reloads_the_application_before_yielding Dispatcher.expects(:reload_application) - reloader = Reloader.new(lambda { + Reloader.run(@lock) do [200, { "Content-Type" => "text/html" }, [""]] - }) - reloader.call({ }) + end + end + + def test_it_locks_before_yielding + lock = DummyMutex.new + Dispatcher.expects(:reload_application) + Reloader.run(lock) do + assert lock.locked? + [200, { "Content-Type" => "text/html" }, [""]] + end + assert lock.locked? + end + + def test_it_unlocks_upon_calling_close_on_body + lock = DummyMutex.new + Dispatcher.expects(:reload_application) + headers, status, body = Reloader.run(lock) do + [200, { "Content-Type" => "text/html" }, [""]] + end + body.close + assert !lock.locked? + end + + def test_it_unlocks_if_app_object_raises_exception + lock = DummyMutex.new + Dispatcher.expects(:reload_application) + assert_raise(RuntimeError) do + Reloader.run(lock) do + raise "oh no!" + end + end + assert !lock.locked? end def test_returned_body_object_always_responds_to_close - body = setup_and_return_body(lambda { + status, headers, body = Reloader.run(@lock) do [200, { "Content-Type" => "text/html" }, [""]] - }) + end assert body.respond_to?(:close) end def test_returned_body_object_behaves_like_underlying_object - body = setup_and_return_body(lambda { + status, headers, body = Reloader.run(@lock) do b = MyBody.new b << "hello" b << "world" [200, { "Content-Type" => "text/html" }, b] - }) + end assert_equal 2, body.size assert_equal "hello", body[0] assert_equal "world", body[1] @@ -60,20 +87,20 @@ class ReloaderTests < ActiveSupport::TestCase def test_it_calls_close_on_underlying_object_when_close_is_called_on_body close_called = false - body = setup_and_return_body(lambda { + status, headers, body = Reloader.run(@lock) do b = MyBody.new do close_called = true end [200, { "Content-Type" => "text/html" }, b] - }) + end body.close assert close_called end def test_returned_body_object_responds_to_all_methods_supported_by_underlying_object - body = setup_and_return_body(lambda { + status, headers, body = Reloader.run(@lock) do [200, { "Content-Type" => "text/html" }, MyBody.new] - }) + end assert body.respond_to?(:size) assert body.respond_to?(:each) assert body.respond_to?(:foo) @@ -82,16 +109,16 @@ class ReloaderTests < ActiveSupport::TestCase def test_it_doesnt_clean_up_the_application_after_call Dispatcher.expects(:cleanup_application).never - body = setup_and_return_body(lambda { + status, headers, body = Reloader.run(@lock) do [200, { "Content-Type" => "text/html" }, MyBody.new] - }) + end end def test_it_cleans_up_the_application_when_close_is_called_on_body Dispatcher.expects(:cleanup_application) - body = setup_and_return_body(lambda { + status, headers, body = Reloader.run(@lock) do [200, { "Content-Type" => "text/html" }, MyBody.new] - }) + end body.close end end diff --git a/vendor/rails/actionpack/test/controller/request/json_params_parsing_test.rb b/vendor/rails/actionpack/test/controller/request/json_params_parsing_test.rb index a3dde72c..db6cf7b3 100644 --- a/vendor/rails/actionpack/test/controller/request/json_params_parsing_test.rb +++ b/vendor/rails/actionpack/test/controller/request/json_params_parsing_test.rb @@ -30,16 +30,36 @@ class JsonParamsParsingTest < ActionController::IntegrationTest ) end + test "logs error if parsing unsuccessful" do + with_test_routing do + begin + $stderr = StringIO.new + json = "[\"person]\": {\"name\": \"David\"}}" + post "/parse", json, {'CONTENT_TYPE' => 'application/json'} + assert_response :error + $stderr.rewind && err = $stderr.read + assert err =~ /Error occurred while parsing request parameters/ + ensure + $stderr = STDERR + end + end + end + private def assert_parses(expected, actual, headers = {}) - with_routing do |set| - set.draw do |map| - map.connect ':action', :controller => "json_params_parsing_test/test" - end - + with_test_routing do post "/parse", actual, headers assert_response :ok assert_equal(expected, TestController.last_request_parameters) end end + + def with_test_routing + with_routing do |set| + set.draw do |map| + map.connect ':action', :controller => "json_params_parsing_test/test" + end + yield + end + end end diff --git a/vendor/rails/actionpack/test/controller/request/xml_params_parsing_test.rb b/vendor/rails/actionpack/test/controller/request/xml_params_parsing_test.rb index ee764e72..170cf77e 100644 --- a/vendor/rails/actionpack/test/controller/request/xml_params_parsing_test.rb +++ b/vendor/rails/actionpack/test/controller/request/xml_params_parsing_test.rb @@ -38,6 +38,21 @@ class XmlParamsParsingTest < ActionController::IntegrationTest end end + test "logs error if parsing unsuccessful" do + with_test_routing do + begin + $stderr = StringIO.new + xml = "David#{ActiveSupport::Base64.encode64('ABC')}" + post "/parse", xml, default_headers + assert_response :error + $stderr.rewind && err = $stderr.read + assert err =~ /Error occurred while parsing request parameters/ + ensure + $stderr = STDERR + end + end + end + test "parses multiple files" do xml = <<-end_body 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 835e73e3..83925ed4 100644 --- a/vendor/rails/actionpack/test/controller/request_forgery_protection_test.rb +++ b/vendor/rails/actionpack/test/controller/request_forgery_protection_test.rb @@ -151,14 +151,10 @@ module RequestForgeryProtectionTests delete :index, :format => 'xml' end end - + def test_should_allow_xhr_post_without_token assert_nothing_raised { xhr :post, :index } end - def test_should_not_allow_xhr_post_with_html_without_token - @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - assert_raise(ActionController::InvalidAuthenticityToken) { xhr :post, :index } - end def test_should_allow_xhr_put_without_token assert_nothing_raised { xhr :put, :index } @@ -168,6 +164,11 @@ module RequestForgeryProtectionTests assert_nothing_raised { xhr :delete, :index } end + def test_should_allow_xhr_post_with_encoded_form_content_type_without_token + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + assert_nothing_raised { xhr :post, :index } + end + def test_should_allow_post_with_token post :index, :authenticity_token => @token assert_response :success diff --git a/vendor/rails/actionpack/test/controller/resources_test.rb b/vendor/rails/actionpack/test/controller/resources_test.rb index 30ab110e..0b639e36 100644 --- a/vendor/rails/actionpack/test/controller/resources_test.rb +++ b/vendor/rails/actionpack/test/controller/resources_test.rb @@ -76,6 +76,50 @@ class ResourcesTest < ActionController::TestCase end end + def test_override_paths_for_member_and_collection_methods + collection_methods = { 'rss' => :get, 'reorder' => :post, 'csv' => :post } + member_methods = { 'rss' => :get, :atom => :get, :upload => :post, :fix => :post } + path_names = {:new => 'nuevo', 'rss' => 'canal', :fix => 'corrigir' } + + with_restful_routing :messages, + :collection => collection_methods, + :member => member_methods, + :path_names => path_names do + + assert_restful_routes_for :messages, + :collection => collection_methods, + :member => member_methods, + :path_names => path_names do |options| + member_methods.each do |action, method| + assert_recognizes(options.merge(:action => action.to_s, :id => '1'), + :path => "/messages/1/#{path_names[action] || action}", + :method => method) + end + + collection_methods.each do |action, method| + assert_recognizes(options.merge(:action => action), + :path => "/messages/#{path_names[action] || action}", + :method => method) + end + end + + assert_restful_named_routes_for :messages, + :collection => collection_methods, + :member => member_methods, + :path_names => path_names do |options| + + collection_methods.keys.each do |action| + assert_named_route "/messages/#{path_names[action] || action}", "#{action}_messages_path", :action => action + end + + member_methods.keys.each do |action| + assert_named_route "/messages/1/#{path_names[action] || action}", "#{action}_message_path", :action => action, :id => "1" + end + + end + end + end + def test_override_paths_for_default_restful_actions resource = ActionController::Resources::Resource.new(:messages, :path_names => {:new => 'nuevo', :edit => 'editar'}) diff --git a/vendor/rails/actionpack/test/controller/routing_test.rb b/vendor/rails/actionpack/test/controller/routing_test.rb index 93759425..e54f2f7b 100644 --- a/vendor/rails/actionpack/test/controller/routing_test.rb +++ b/vendor/rails/actionpack/test/controller/routing_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'controller/fake_controllers' +require 'action_controller/routing/route_set' class MilestonesController < ActionController::Base def index() head :ok end @@ -742,7 +743,7 @@ class MockController end end -class LegacyRouteSetTests < Test::Unit::TestCase +class LegacyRouteSetTests < ActiveSupport::TestCase attr_reader :rs def setup @@ -758,6 +759,10 @@ class LegacyRouteSetTests < Test::Unit::TestCase @rs.clear! end + def test_routes_for_controller_and_action_deprecated + assert_deprecated { @rs.routes_for_controller_and_action("controller", "action") } + end + def test_default_setup @rs.draw {|m| m.connect ':controller/:action/:id' } assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content")) @@ -1605,7 +1610,7 @@ class RouteTest < Test::Unit::TestCase end end -class RouteSetTest < Test::Unit::TestCase +class RouteSetTest < ActiveSupport::TestCase def set @set ||= ROUTING::RouteSet.new end diff --git a/vendor/rails/actionpack/test/controller/send_file_test.rb b/vendor/rails/actionpack/test/controller/send_file_test.rb index 26c7f9bd..f61bbc60 100644 --- a/vendor/rails/actionpack/test/controller/send_file_test.rb +++ b/vendor/rails/actionpack/test/controller/send_file_test.rb @@ -1,9 +1,10 @@ +# encoding: utf-8 require 'abstract_unit' module TestFileUtils def file_name() File.basename(__FILE__) end def file_path() File.expand_path(__FILE__) end - def file_data() File.open(file_path, 'rb') { |f| f.read } end + def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end end class SendFileController < ActionController::Base @@ -15,6 +16,7 @@ class SendFileController < ActionController::Base def file() send_file(file_path, options) end def data() send_data(file_data, options) end + def multibyte_text_data() send_data("Кирилица\n祝您好運", options) end def rescue_action(e) raise end end @@ -49,6 +51,7 @@ class SendFileTest < ActionController::TestCase require 'stringio' output = StringIO.new output.binmode + output.string.force_encoding(file_data.encoding) if output.string.respond_to?(:force_encoding) assert_nothing_raised { response.body.call(response, output) } assert_equal file_data, output.string end @@ -158,4 +161,11 @@ class SendFileTest < ActionController::TestCase assert_equal ActionController::Base::DEFAULT_RENDER_STATUS_CODE, @response.status end end + + def test_send_data_content_length_header + @controller.headers = {} + @controller.options = { :type => :text, :filename => 'file_with_utf8_text' } + process('multibyte_text_data') + assert_equal '29', @controller.headers['Content-Length'] + end end diff --git a/vendor/rails/actionpack/test/controller/url_rewriter_test.rb b/vendor/rails/actionpack/test/controller/url_rewriter_test.rb index 863f8414..fdc4cfa3 100644 --- a/vendor/rails/actionpack/test/controller/url_rewriter_test.rb +++ b/vendor/rails/actionpack/test/controller/url_rewriter_test.rb @@ -46,6 +46,20 @@ class UrlRewriterTests < ActionController::TestCase ) end + def test_anchor_should_call_to_param + assert_equal( + 'http://test.host/c/a/i#anchor', + @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor')) + ) + end + + def test_anchor_should_be_cgi_escaped + assert_equal( + 'http://test.host/c/a/i#anc%2Fhor', + @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor')) + ) + end + def test_overwrite_params @params[:controller] = 'hi' @params[:action] = 'bye' @@ -110,6 +124,18 @@ class UrlWriterTests < ActionController::TestCase ) end + def test_anchor_should_call_to_param + assert_equal('/c/a#anchor', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor')) + ) + end + + def test_anchor_should_be_cgi_escaped + assert_equal('/c/a#anc%2Fhor', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor')) + ) + end + def test_default_host add_host! assert_equal('http://www.basecamphq.com/c/a/i', @@ -304,7 +330,7 @@ class UrlWriterTests < ActionController::TestCase def test_named_routes_with_nil_keys ActionController::Routing::Routes.clear! ActionController::Routing::Routes.draw do |map| - map.main '', :controller => 'posts' + map.main '', :controller => 'posts', :format => nil map.resources :posts map.connect ':controller/:action/:id' end @@ -314,9 +340,9 @@ class UrlWriterTests < ActionController::TestCase controller = kls.new params = {:action => :index, :controller => :posts, :format => :xml} - assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params)) + assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params)) params[:format] = nil - assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params)) + assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params)) ensure ActionController::Routing::Routes.load! end diff --git a/vendor/rails/actionpack/test/fixtures/public/absolute/test.css b/vendor/rails/actionpack/test/fixtures/public/absolute/test.css new file mode 100644 index 00000000..6e09025b --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/public/absolute/test.css @@ -0,0 +1,23 @@ +/* bank.css */ + +/* robber.css */ + +/* version.1.0.css */ + +/* bank.css */ + +/* bank.css */ + +/* robber.css */ + +/* version.1.0.css */ + +/* bank.css */ + +/* robber.css */ + +/* version.1.0.css */ + +/* robber.css */ + +/* version.1.0.css */ \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/public/absolute/test.js b/vendor/rails/actionpack/test/fixtures/public/absolute/test.js new file mode 100644 index 00000000..4c4292a3 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/public/absolute/test.js @@ -0,0 +1,63 @@ +// prototype js + +// effects js + +// dragdrop js + +// controls js + +// prototype js + +// effects js + +// dragdrop js + +// controls js + +// application js + +// bank js + +// robber js + +// version.1.0 js + +// application js + +// bank js + +// prototype js + +// effects js + +// dragdrop js + +// controls js + +// prototype js + +// effects js + +// dragdrop js + +// controls js + +// application js + +// bank js + +// robber js + +// version.1.0 js + +// application js + +// bank js + +// robber js + +// version.1.0 js + +// robber js + +// version.1.0 js \ No newline at end of file diff --git a/vendor/rails/actionpack/test/template/atom_feed_helper_test.rb b/vendor/rails/actionpack/test/template/atom_feed_helper_test.rb index 8a00a397..01a4178b 100644 --- a/vendor/rails/actionpack/test/template/atom_feed_helper_test.rb +++ b/vendor/rails/actionpack/test/template/atom_feed_helper_test.rb @@ -150,6 +150,26 @@ class ScrollsController < ActionController::Base end end EOT + FEEDS["provide_builder"] = <<-'EOT' + # we pass in the new_xml to the helper so it doesn't + # call anything on the original builder + new_xml = Builder::XmlMarkup.new(:target=>'') + atom_feed(:xml => new_xml) do |feed| + feed.title("My great blog!") + feed.updated((@scrolls.first.created_at)) + + for scroll in @scrolls + feed.entry(scroll) do |entry| + entry.title(scroll.title) + entry.content(scroll.body, :type => 'html') + + entry.author do |author| + author.name("DHH") + end + end + end + end + EOT def index @scrolls = [ Scroll.new(1, "1", "Hello One", "Something COOL!", Time.utc(2007, 12, 12, 15), Time.utc(2007, 12, 12, 15)), @@ -194,6 +214,15 @@ class AtomFeedTest < ActionController::TestCase end end + def test_providing_builder_to_atom_feed + with_restful_routing(:scrolls) do + get :index, :id=>"provide_builder" + # because we pass in the non-default builder, the content generated by the + # helper should go 'nowhere'. Leaving the response body blank. + assert @response.body.blank? + end + end + def test_entry_with_prefilled_options_should_use_those_instead_of_querying_the_record with_restful_routing(:scrolls) do get :index, :id => "entry_options" diff --git a/vendor/rails/actionpack/test/template/form_helper_test.rb b/vendor/rails/actionpack/test/template/form_helper_test.rb index e92f62d8..a32cdbe8 100644 --- a/vendor/rails/actionpack/test/template/form_helper_test.rb +++ b/vendor/rails/actionpack/test/template/form_helper_test.rb @@ -145,6 +145,22 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal('', label(:post, :title, nil, "for" => "my_for")) end + def test_label_with_id_attribute_as_symbol + assert_dom_equal('', label(:post, :title, nil, :id => "my_id")) + end + + def test_label_with_id_attribute_as_string + assert_dom_equal('', label(:post, :title, nil, "id" => "my_id")) + end + + def test_label_with_for_and_id_attributes_as_symbol + assert_dom_equal('', label(:post, :title, nil, :for => "my_for", :id => "my_id")) + end + + def test_label_with_for_and_id_attributes_as_string + assert_dom_equal('', label(:post, :title, nil, "for" => "my_for", "id" => "my_id")) + end + def test_label_for_radio_buttons_with_value assert_dom_equal('', label("post", "title", "The title goes here", :value => "great_title")) assert_dom_equal('', label("post", "title", "The title goes here", :value => "great title")) @@ -295,6 +311,16 @@ class FormHelperTest < ActionView::TestCase ) end + def test_radio_button_with_booleans + assert_dom_equal('', + radio_button("post", "secret", true) + ) + + assert_dom_equal('', + radio_button("post", "secret", false) + ) + end + def test_text_area assert_dom_equal( '', diff --git a/vendor/rails/actionpack/test/template/form_options_helper_i18n_test.rb b/vendor/rails/actionpack/test/template/form_options_helper_i18n_test.rb new file mode 100644 index 00000000..91e370ef --- /dev/null +++ b/vendor/rails/actionpack/test/template/form_options_helper_i18n_test.rb @@ -0,0 +1,27 @@ +require 'abstract_unit' + +class FormOptionsHelperI18nTests < ActionView::TestCase + tests ActionView::Helpers::FormOptionsHelper + + def setup + @prompt_message = 'Select!' + I18n.backend.send(:init_translations) + I18n.backend.store_translations :en, :support => { :select => { :prompt => @prompt_message } } + end + + def teardown + I18n.backend = I18n::Backend::Simple.new + end + + def test_select_with_prompt_true_translates_prompt_message + I18n.expects(:translate).with('support.select.prompt', { :default => 'Please select' }) + select('post', 'category', [], :prompt => true) + end + + def test_select_with_translated_prompt + assert_dom_equal( + %Q(), + select('post', 'category', [], :prompt => true) + ) + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/template/form_options_helper_test.rb b/vendor/rails/actionpack/test/template/form_options_helper_test.rb index 73624406..aa40e46a 100644 --- a/vendor/rails/actionpack/test/template/form_options_helper_test.rb +++ b/vendor/rails/actionpack/test/template/form_options_helper_test.rb @@ -763,6 +763,40 @@ class FormOptionsHelperTest < ActionView::TestCase html end + def test_grouped_collection_select + @continents = [ + Continent.new("", [Country.new("", ""), Country.new("so", "Somalia")] ), + Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) + ] + + @post = Post.new + @post.origin = 'dk' + + assert_dom_equal( + %Q{}, + grouped_collection_select("post", "origin", @continents, :countries, :continent_name, :country_id, :country_name) + ) + end + + def test_grouped_collection_select_under_fields_for + @continents = [ + Continent.new("", [Country.new("", ""), Country.new("so", "Somalia")] ), + Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) + ] + + @post = Post.new + @post.origin = 'dk' + + fields_for :post, @post do |f| + concat f.grouped_collection_select("origin", @continents, :countries, :continent_name, :country_id, :country_name) + end + + assert_dom_equal( + %Q{}, + output_buffer + ) + end + private def dummy_posts diff --git a/vendor/rails/actionpack/test/template/text_helper_test.rb b/vendor/rails/actionpack/test/template/text_helper_test.rb index a370f145..715c390c 100644 --- a/vendor/rails/actionpack/test/template/text_helper_test.rb +++ b/vendor/rails/actionpack/test/template/text_helper_test.rb @@ -1,5 +1,10 @@ require 'abstract_unit' require 'testing_sandbox' +begin + require 'redcloth' +rescue LoadError + $stderr.puts "Skipping textilize tests. `gem install RedCloth` to enable." +end class TextHelperTest < ActionView::TestCase tests ActionView::Helpers::TextHelper @@ -517,4 +522,22 @@ class TextHelperTest < ActionView::TestCase assert_equal("red", cycle("red", "blue")) assert_equal(%w{Specialized Fuji Giant}, @cycles) end + + if defined? RedCloth + def test_textilize + assert_equal("

This is Textile! Rejoice!

", textilize("*This is Textile!* Rejoice!")) + end + + def test_textilize_with_blank + assert_equal("", textilize("")) + end + + def test_textilize_with_options + assert_equal("

This is worded <strong>strongly</strong>

", textilize("This is worded strongly", :filter_html)) + end + + def test_textilize_with_hard_breaks + assert_equal("

This is one scary world.
\n True.

", textilize("This is one scary world.\n True.")) + end + end end diff --git a/vendor/rails/actionpack/test/template/url_helper_test.rb b/vendor/rails/actionpack/test/template/url_helper_test.rb index 5900709d..82292cb7 100644 --- a/vendor/rails/actionpack/test/template/url_helper_test.rb +++ b/vendor/rails/actionpack/test/template/url_helper_test.rb @@ -219,6 +219,14 @@ class UrlHelperTest < ActionView::TestCase ) end + def test_link_tag_using_delete_javascript_and_href_and_confirm + assert_dom_equal( + "Destroy", + link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#', :confirm => "Are you serious?"), + "When specifying url, form should be generated with it, but not this.href" + ) + end + def test_link_tag_using_post_javascript_and_popup assert_raise(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") } end diff --git a/vendor/rails/activerecord/CHANGELOG b/vendor/rails/activerecord/CHANGELOG index 5593a22e..12618e2a 100644 --- a/vendor/rails/activerecord/CHANGELOG +++ b/vendor/rails/activerecord/CHANGELOG @@ -1,3 +1,10 @@ +*2.3.4 (September 4, 2009)* + +* PostgreSQL: XML datatype support. #1874 [Leonardo Borges] + +* SQLite: deprecate the 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper] + + *2.3.3 (July 12, 2009)* * Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha] diff --git a/vendor/rails/activerecord/Rakefile b/vendor/rails/activerecord/Rakefile index 78550536..71b2b511 100644 --- a/vendor/rails/activerecord/Rakefile +++ b/vendor/rails/activerecord/Rakefile @@ -24,14 +24,30 @@ PKG_FILES = FileList[ "lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile" ].exclude(/\bCVS\b|~$/) +def run_without_aborting(*tasks) + errors = [] + + tasks.each do |task| + begin + Rake::Task[task].invoke + rescue Exception + errors << task + end + end + + abort "Errors running #{errors.join(', ')}" if errors.any? +end desc 'Run mysql, sqlite, and postgresql tests by default' task :default => :test desc 'Run mysql, sqlite, and postgresql tests' -task :test => defined?(JRUBY_VERSION) ? - %w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) : - %w(test_mysql test_sqlite3 test_postgresql) +task :test do + tasks = defined?(JRUBY_VERSION) ? + %w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) : + %w(test_mysql test_sqlite3 test_postgresql) + run_without_aborting(*tasks) +end for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ) Rake::TestTask.new("test_#{adapter}") { |t| @@ -53,8 +69,8 @@ end namespace :mysql do desc 'Build the MySQL test databases' task :build_databases do - %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest ) - %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest2 ) + %x( echo "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER}) + %x( echo "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER}) end desc 'Drop the MySQL test databases' @@ -75,8 +91,8 @@ task :rebuild_mysql_databases => 'mysql:rebuild_databases' namespace :postgresql do desc 'Build the PostgreSQL test databases' task :build_databases do - %x( createdb activerecord_unittest ) - %x( createdb activerecord_unittest2 ) + %x( createdb -E UTF8 activerecord_unittest ) + %x( createdb -E UTF8 activerecord_unittest2 ) end desc 'Drop the PostgreSQL test databases' @@ -176,7 +192,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.3' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.4' + 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/examples/.gitignore b/vendor/rails/activerecord/examples/.gitignore new file mode 100644 index 00000000..0dfc1cb7 --- /dev/null +++ b/vendor/rails/activerecord/examples/.gitignore @@ -0,0 +1 @@ +performance.sql diff --git a/vendor/rails/activerecord/examples/performance.rb b/vendor/rails/activerecord/examples/performance.rb new file mode 100755 index 00000000..53acd62f --- /dev/null +++ b/vendor/rails/activerecord/examples/performance.rb @@ -0,0 +1,162 @@ +#!/usr/bin/env ruby -KU + +TIMES = (ENV['N'] || 10000).to_i + +require 'rubygems' +gem 'addressable', '~>2.0' +gem 'faker', '~>0.3.1' +gem 'rbench', '~>0.2.3' +require 'addressable/uri' +require 'faker' +require 'rbench' + +__DIR__ = File.dirname(__FILE__) +$:.unshift "#{__DIR__}/../lib" +require 'active_record' + +conn = { :adapter => 'mysql', + :database => 'activerecord_unittest', + :username => 'rails', :password => '', + :encoding => 'utf8' } + +conn[:socket] = Pathname.glob(%w[ + /opt/local/var/run/mysql5/mysqld.sock + /tmp/mysqld.sock + /tmp/mysql.sock + /var/mysql/mysql.sock + /var/run/mysqld/mysqld.sock +]).find { |path| path.socket? } + +ActiveRecord::Base.establish_connection(conn) + +class User < ActiveRecord::Base + connection.create_table :users, :force => true do |t| + t.string :name, :email + t.timestamps + end + + has_many :exhibits +end + +class Exhibit < ActiveRecord::Base + connection.create_table :exhibits, :force => true do |t| + t.belongs_to :user + t.string :name + t.text :notes + t.timestamps + end + + belongs_to :user + + def look; attributes end + def feel; look; user.name end + + def self.look(exhibits) exhibits.each { |e| e.look } end + def self.feel(exhibits) exhibits.each { |e| e.feel } end +end + +sqlfile = "#{__DIR__}/performance.sql" + +if File.exists?(sqlfile) + mysql_bin = %w[mysql mysql5].select { |bin| `which #{bin}`.length > 0 } + `#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}` +else + puts 'Generating data...' + + # pre-compute the insert statements and fake data compilation, + # so the benchmarks below show the actual runtime for the execute + # method, minus the setup steps + + # Using the same paragraph for all exhibits because it is very slow + # to generate unique paragraphs for all exhibits. + notes = Faker::Lorem.paragraphs.join($/) + today = Date.today + + puts 'Inserting 10,000 users and exhibits...' + 10_000.times do + user = User.create( + :created_at => today, + :name => Faker::Name.name, + :email => Faker::Internet.email + ) + + Exhibit.create( + :created_at => today, + :name => Faker::Company.name, + :user => user, + :notes => notes + ) + end + + mysqldump_bin = %w[mysqldump mysqldump5].select { |bin| `which #{bin}`.length > 0 } + `#{mysqldump_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} exhibits users > #{sqlfile}` +end + +RBench.run(TIMES) do + column :times + column :ar + + report 'Model#id', (TIMES * 100).ceil do + ar_obj = Exhibit.find(1) + + ar { ar_obj.id } + end + + report 'Model.new (instantiation)' do + ar { Exhibit.new } + end + + report 'Model.new (setting attributes)' do + attrs = { :name => 'sam' } + ar { Exhibit.new(attrs) } + end + + report 'Model.first' do + ar { Exhibit.first.look } + end + + report 'Model.all limit(100)', (TIMES / 10).ceil do + ar { Exhibit.look Exhibit.all(:limit => 100) } + end + + report 'Model.all limit(100) with relationship', (TIMES / 10).ceil do + ar { Exhibit.feel Exhibit.all(:limit => 100, :include => :user) } + end + + report 'Model.all limit(10,000)', (TIMES / 1000).ceil do + ar { Exhibit.look Exhibit.all(:limit => 10000) } + end + + exhibit = { + :name => Faker::Company.name, + :notes => Faker::Lorem.paragraphs.join($/), + :created_at => Date.today + } + + report 'Model.create' do + ar { Exhibit.create(exhibit) } + end + + report 'Resource#attributes=' do + attrs_first = { :name => 'sam' } + attrs_second = { :name => 'tom' } + ar { exhibit = Exhibit.new(attrs_first); exhibit.attributes = attrs_second } + end + + report 'Resource#update' do + ar { Exhibit.first.update_attributes(:name => 'bob') } + end + + report 'Resource#destroy' do + ar { Exhibit.first.destroy } + end + + report 'Model.transaction' do + ar { Exhibit.transaction { Exhibit.new } } + end + + summary 'Total' +end + +ActiveRecord::Migration.drop_table "exhibits" +ActiveRecord::Migration.drop_table "users" diff --git a/vendor/rails/activerecord/lib/active_record/associations.rb b/vendor/rails/activerecord/lib/active_record/associations.rb index 66db63ac..a08119c6 100755 --- a/vendor/rails/activerecord/lib/active_record/associations.rb +++ b/vendor/rails/activerecord/lib/active_record/associations.rb @@ -34,11 +34,13 @@ module ActiveRecord end end - class HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc: + class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.") end end + HasManyThroughCantAssociateThroughHasManyReflection = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection', 'ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection') + class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.") @@ -410,6 +412,32 @@ module ActiveRecord # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm # @firm.invoices # selects all invoices by going through the Client join model. # + # Similarly you can go through a +has_one+ association on the join model: + # + # class Group < ActiveRecord::Base + # has_many :users + # has_many :avatars, :through => :users + # end + # + # class User < ActiveRecord::Base + # belongs_to :group + # has_one :avatar + # end + # + # class Avatar < ActiveRecord::Base + # belongs_to :user + # end + # + # @group = Group.first + # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group + # @group.avatars # selects all avatars by going through the User join model. + # + # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are + # *read-only*. For example, the following would not work following the previous example: + # + # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around. + # @group.avatars.delete(@group.avatars.last) # so would this + # # === Polymorphic Associations # # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they @@ -759,7 +787,7 @@ module ActiveRecord # [:through] # Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key # are ignored, as the association uses the source reflection. You can only use a :through query through a belongs_to - # or has_many association on the join model. + # has_one or has_many association on the join model. # [:source] # Specifies the source association name used by has_many :through queries. Only use it if the name cannot be # inferred from the association. has_many :subscribers, :through => :subscriptions will look for either :subscribers or @@ -1241,7 +1269,11 @@ module ActiveRecord if association_proxy_class == HasOneThroughAssociation association.create_through_record(new_value) - self.send(reflection.name, new_value) + if new_record? + association_instance_set(reflection.name, new_value.nil? ? nil : association) + else + self.send(reflection.name, new_value) + end else association.replace(new_value) association_instance_set(reflection.name, new_value.nil? ? nil : association) @@ -1293,7 +1325,7 @@ module ActiveRecord define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| ids = (new_value || []).reject { |nid| nid.blank? } - send("#{reflection.name}=", reflection.class_name.constantize.find(ids)) + send("#{reflection.name}=", reflection.klass.find(ids)) end end end @@ -1838,7 +1870,7 @@ module ActiveRecord descendant end.flatten.compact - remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty? + remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty? end end end 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 3aef1b21..42b6e5de 100644 --- a/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb +++ b/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb @@ -208,6 +208,7 @@ module ActiveRecord # Note that this method will _always_ remove records from the database # ignoring the +:dependent+ option. def destroy(*records) + records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)} remove_records(records) do |records, old_records| old_records.each { |record| record.destroy } end diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index af9ce3df..cb60baa7 100644 --- a/vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -1,6 +1,11 @@ module ActiveRecord module Associations class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc: + def initialize(owner, reflection) + super + @primary_key_list = {} + end + def create(attributes = {}) create_record(attributes) { |record| insert_record(record) } end @@ -17,6 +22,12 @@ module ActiveRecord @reflection.reset_column_information end + def has_primary_key? + return @has_primary_key unless @has_primary_key.nil? + @has_primary_key = (ActiveRecord::Base.connection.supports_primary_key? && + ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table])) + end + protected def construct_find_options!(options) options[:joins] = @join_sql @@ -29,6 +40,11 @@ module ActiveRecord end def insert_record(record, force = true, validate = true) + if has_primary_key? + raise ActiveRecord::ConfigurationError, + "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})." + end + if record.new_record? if force record.save! diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb index a2cbabfe..1f673ad7 100644 --- a/vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb +++ b/vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb @@ -74,6 +74,7 @@ module ActiveRecord "#{@reflection.primary_key_name} = NULL", "#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})" ) + @owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter? end end 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 1c091e7d..6af14a57 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 @@ -17,7 +17,17 @@ module ActiveRecord def create(attrs = nil) transaction do - self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association) + object = if attrs + @reflection.klass.send(:with_scope, :create => attrs) { + @reflection.create_association + } + else + @reflection.create_association + end + raise_on_type_mismatch(object) + add_record_to_target_with_callbacks(object) do |r| + insert_record(object, false) + end object end end @@ -44,7 +54,7 @@ module ActiveRecord options[:select] = construct_select(options[:select]) options[:from] ||= construct_from options[:joins] = construct_joins(options[:joins]) - options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? + options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include] end def insert_record(record, force = true, validate = true) @@ -96,7 +106,7 @@ module ActiveRecord # Construct attributes for :through pointing to owner and associate. def construct_join_attributes(associate) # TODO: revist this to allow it for deletion, supposing dependent option is supported - raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many + raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro) join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id) if @reflection.options[:source_type] join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s) diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_one_through_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_one_through_association.rb index d93c8e78..d8bcf76a 100644 --- a/vendor/rails/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/vendor/rails/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -9,8 +9,14 @@ module ActiveRecord if current_object new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy - else - @owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value))) if new_value + elsif new_value + if @owner.new_record? + self.target = new_value + through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name) + through_association.build(construct_join_attributes(new_value)) + else + @owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value))) + end end end diff --git a/vendor/rails/activerecord/lib/active_record/autosave_association.rb b/vendor/rails/activerecord/lib/active_record/autosave_association.rb index 4f63b52c..1b73b0fd 100644 --- a/vendor/rails/activerecord/lib/active_record/autosave_association.rb +++ b/vendor/rails/activerecord/lib/active_record/autosave_association.rb @@ -249,9 +249,10 @@ module ActiveRecord unless valid = association.valid? if reflection.options[:autosave] unless association.marked_for_destruction? - association.errors.each do |attribute, message| - attribute = "#{reflection.name}_#{attribute}" - errors.add(attribute, message) unless errors.on(attribute) + association.errors.each_error do |attribute, error| + error = error.dup + error.attribute = "#{reflection.name}_#{attribute}" + errors.add(error) unless errors.on(error.attribute) end end else diff --git a/vendor/rails/activerecord/lib/active_record/base.rb b/vendor/rails/activerecord/lib/active_record/base.rb index d155837d..c17702d5 100755 --- a/vendor/rails/activerecord/lib/active_record/base.rb +++ b/vendor/rails/activerecord/lib/active_record/base.rb @@ -1364,7 +1364,7 @@ module ActiveRecord #:nodoc: end defaults << options[:default] if options[:default] defaults.flatten! - defaults << attribute_key_name.humanize + defaults << attribute_key_name.to_s.humanize options[:count] ||= 1 I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes])) end @@ -2294,20 +2294,24 @@ module ActiveRecord #:nodoc: # And for value objects on a composed_of relationship: # { :address => Address.new("123 abc st.", "chicago") } # # => "address_street='123 abc st.' and address_city='chicago'" - def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name) + def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name) attrs = expand_hash_conditions_for_aggregates(attrs) conditions = attrs.map do |attr, value| + table_name = default_table_name + unless value.is_a?(Hash) attr = attr.to_s # Extract table name from qualified attribute names. if attr.include?('.') - table_name, attr = attr.split('.', 2) - table_name = connection.quote_table_name(table_name) + attr_table_name, attr = attr.split('.', 2) + attr_table_name = connection.quote_table_name(attr_table_name) + else + attr_table_name = table_name end - attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", value) + attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value) else sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s)) end @@ -3028,16 +3032,22 @@ module ActiveRecord #:nodoc: def execute_callstack_for_multiparameter_attributes(callstack) errors = [] - callstack.each do |name, values| + callstack.each do |name, values_with_empty_parameters| begin klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass + # in order to allow a date to be set without a year, we must keep the empty values. + # Otherwise, we wouldn't be able to distinguish it from a date with an empty day. + values = values_with_empty_parameters.reject(&:nil?) + if values.empty? send(name + "=", nil) else + value = if Time == klass instantiate_time_object(name, values) elsif Date == klass begin + values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end Date.new(*values) rescue ArgumentError => ex # if Date.new raises an exception on an invalid date instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates @@ -3065,10 +3075,8 @@ module ActiveRecord #:nodoc: attribute_name = multiparameter_name.split("(").first attributes[attribute_name] = [] unless attributes.include?(attribute_name) - unless value.empty? - attributes[attribute_name] << - [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ] - end + parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) + attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ] end attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } } diff --git a/vendor/rails/activerecord/lib/active_record/calculations.rb b/vendor/rails/activerecord/lib/active_record/calculations.rb index 7af97d72..eb149e8c 100644 --- a/vendor/rails/activerecord/lib/active_record/calculations.rb +++ b/vendor/rails/activerecord/lib/active_record/calculations.rb @@ -190,6 +190,8 @@ module ActiveRecord sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] if options[:from] sql << " FROM #{options[:from]} " + elsif scope && scope[:from] && !use_workaround + sql << " FROM #{scope[:from]} " else sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround sql << " FROM #{connection.quote_table_name(table_name)} " diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 24c734cd..520f3c8c 100644 --- a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -277,7 +277,6 @@ module ActiveRecord add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key column_sql end - alias to_s :to_sql private @@ -316,6 +315,20 @@ module ActiveRecord @base = base end + #Handles non supported datatypes - e.g. XML + def method_missing(symbol, *args) + if symbol.to_s == 'xml' + xml_column_fallback(args) + end + end + + def xml_column_fallback(*args) + case @base.adapter_name.downcase + when 'sqlite', 'mysql' + options = args.extract_options! + column(args[0], :text, options) + end + end # Appends a primary key definition to the table definition. # Can be called multiple times, but this is probably not a good idea. def primary_key(name) @@ -508,7 +521,7 @@ module ActiveRecord # concatenated together. This string can then be prepended and appended to # to generate the final SQL to create the table. def to_sql - @columns * ', ' + @columns.map(&:to_sql) * ', ' end private @@ -706,3 +719,4 @@ module ActiveRecord end end + diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index c29c1562..7e820998 100644 --- a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -99,7 +99,7 @@ module ActiveRecord # See also TableDefinition#column for details on how to create columns. def create_table(table_name, options = {}) table_definition = TableDefinition.new(self) - table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name)) unless options[:id] == false + table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false yield table_definition @@ -321,7 +321,7 @@ module ActiveRecord schema_migrations_table.column :version, :string, :null => false end add_index sm_table, :version, :unique => true, - :name => 'unique_schema_migrations' + :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}" # Backwards-compatibility: if we find schema_info, assume we've # migrated up to that point: diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index a8cd9f03..22871f2b 100755 --- a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -54,6 +54,13 @@ module ActiveRecord false end + # Can this adapter determine the primary key for tables not attached + # to an ActiveRecord class, such as join tables? Backend specific, as + # the abstract adapter always returns +false+. + def supports_primary_key? + false + end + # Does this adapter support using DISTINCT within COUNT? This is +true+ # for all adapters except sqlite. def supports_count_distinct? diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 9300df28..12592787 100644 --- a/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -52,12 +52,7 @@ module ActiveRecord socket = config[:socket] username = config[:username] ? config[:username].to_s : 'root' password = config[:password].to_s - - if config.has_key?(:database) - database = config[:database] - else - raise ArgumentError, "No database specified. Missing argument: database." - end + database = config[:database] # Require the MySQL driver and define Mysql::Result.all_hashes unless defined? Mysql @@ -80,7 +75,7 @@ module ActiveRecord module ConnectionAdapters class MysqlColumn < Column #:nodoc: def extract_default(default) - if type == :binary || type == :text + if sql_type =~ /blob/i || type == :text if default.blank? return null ? nil : '' else @@ -94,7 +89,7 @@ module ActiveRecord end def has_default? - return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns + return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns super end @@ -212,6 +207,10 @@ module ActiveRecord true end + def supports_primary_key? #:nodoc: + true + end + def supports_savepoints? #:nodoc: true end @@ -554,6 +553,12 @@ module ActiveRecord keys.length == 1 ? [keys.first, nil] : nil end + # Returns just a table's primary key + def primary_key(table) + pk_and_sequence = pk_and_sequence_for(table) + pk_and_sequence && pk_and_sequence.first + end + def case_sensitive_equality_operator "= BINARY" end @@ -573,6 +578,10 @@ module ActiveRecord @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) end + @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout] + @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout] + @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout] + @connection.real_connect(*@connection_options) # reconnect must be set after real_connect is called, because real_connect sets it to false internally diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 8440223d..bc289ff5 100644 --- a/vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -39,6 +39,12 @@ module ActiveRecord end module ConnectionAdapters + class TableDefinition + def xml(*args) + options = args.extract_options! + column(args[0], 'xml', options) + end + end # PostgreSQL-specific extensions to column definitions in a table. class PostgreSQLColumn < Column #:nodoc: # Instantiates a new PostgreSQL column definition in a table. @@ -67,7 +73,7 @@ module ActiveRecord # depending on the server specifics super end - + # Maps PostgreSQL-specific data types to logical Rails types. def simplified_type(field_type) case field_type @@ -99,10 +105,10 @@ module ActiveRecord :string # XML type when /^xml$/ - :string + :xml # Arrays when /^\D+\[\]$/ - :string + :string # Object identifier types when /^oid$/ :integer @@ -111,7 +117,7 @@ module ActiveRecord super end end - + # Extracts the value from a PostgreSQL column default definition. def self.extract_value_from_default(default) case default @@ -194,7 +200,8 @@ module ActiveRecord :time => { :name => "time" }, :date => { :name => "date" }, :binary => { :name => "bytea" }, - :boolean => { :name => "boolean" } + :boolean => { :name => "boolean" }, + :xml => { :name => "xml" } } # Returns 'PostgreSQL' as adapter name for identification purposes. @@ -249,6 +256,11 @@ module ActiveRecord true end + # Does PostgreSQL support finding primary key on non-ActiveRecord tables? + def supports_primary_key? #:nodoc: + true + end + # Does PostgreSQL support standard conforming strings? def supports_standard_conforming_strings? # Temporarily set the client message level above error to prevent unintentional @@ -272,7 +284,7 @@ module ActiveRecord def supports_ddl_transactions? true end - + def supports_savepoints? true end @@ -364,7 +376,7 @@ module ActiveRecord if value.kind_of?(String) && column && column.type == :binary "#{quoted_string_prefix}'#{escape_bytea(value)}'" elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/ - "xml '#{quote_string(value)}'" + "xml E'#{quote_string(value)}'" elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/ # Not truly string input, so doesn't require (or allow) escape string syntax. "'#{value.to_s}'" @@ -563,7 +575,7 @@ module ActiveRecord def rollback_db_transaction execute "ROLLBACK" end - + if defined?(PGconn::PQTRANS_IDLE) # The ruby-pg driver supports inspecting the transaction status, # while the ruby-postgres driver does not. @@ -810,6 +822,12 @@ module ActiveRecord nil end + # Returns just a table's primary key + def primary_key(table) + pk_and_sequence = pk_and_sequence_for(table) + pk_and_sequence && pk_and_sequence.first + end + # Renames a table. def rename_table(name, new_name) execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" @@ -908,18 +926,18 @@ module ActiveRecord sql = "DISTINCT ON (#{columns}) #{columns}, " sql << order_columns * ', ' end - + # Returns an ORDER BY clause for the passed order option. - # + # # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this # by wrapping the +sql+ string as a sub-select and ordering in that query. def add_order_by_for_association_limiting!(sql, options) #:nodoc: return sql if options[:order].blank? - + order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?) order.map! { |s| 'DESC' if s =~ /\bdesc$/i } order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ') - + sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}" end @@ -1032,7 +1050,7 @@ module ActiveRecord if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID # Because money output is formatted according to the locale, there are two # cases to consider (note the decimal separators): - # (1) $12,345,678.12 + # (1) $12,345,678.12 # (2) $12.345.678,12 case column = row[cell_index] when /^-?\D+[\d,]+\.\d{2}$/ # (1) @@ -1092,3 +1110,4 @@ module ActiveRecord end 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 69f79755..0bf97a9b 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 @@ -16,6 +16,10 @@ module ActiveRecord db.results_as_hash = true if defined? SQLite::Version db.type_translation = false + message = "Support for SQLite2Adapter and DeprecatedSQLiteAdapter has been removed from Rails 3. " + message << "You should migrate to SQLite 3+ or use the plugin from git://github.com/rails/sqlite2_adapter.git with Rails 3." + ActiveSupport::Deprecation.warn(message) + # "Downgrade" deprecated sqlite API if SQLite.const_defined?(:Version) ConnectionAdapters::SQLite2Adapter.new(db, logger, config) @@ -27,6 +31,10 @@ module ActiveRecord private def parse_sqlite_config!(config) + if config.include?(:dbfile) + ActiveSupport::Deprecation.warn "Please update config/database.yml to use 'database' instead of 'dbfile'" + end + config[:database] ||= config[:dbfile] # Require database. unless config[:database] @@ -104,6 +112,10 @@ module ActiveRecord true end + def supports_primary_key? #:nodoc: + true + end + def requires_reloading? true end diff --git a/vendor/rails/activerecord/lib/active_record/dirty.rb b/vendor/rails/activerecord/lib/active_record/dirty.rb index 4a2510aa..f1896516 100644 --- a/vendor/rails/activerecord/lib/active_record/dirty.rb +++ b/vendor/rails/activerecord/lib/active_record/dirty.rb @@ -143,7 +143,7 @@ module ActiveRecord if partial_updates? # Serialized attributes should always be written in case they've been # changed in place. - update_without_dirty(changed | self.class.serialized_attributes.keys) + update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys)) else update_without_dirty end diff --git a/vendor/rails/activerecord/lib/active_record/fixtures.rb b/vendor/rails/activerecord/lib/active_record/fixtures.rb index 77f0931e..09da78c3 100644 --- a/vendor/rails/activerecord/lib/active_record/fixtures.rb +++ b/vendor/rails/activerecord/lib/active_record/fixtures.rb @@ -621,7 +621,8 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash) targets.each do |target| join_fixtures["#{label}_#{target}"] = Fixture.new( { association.primary_key_name => row[primary_key_name], - association.association_foreign_key => Fixtures.identify(target) }, nil) + association.association_foreign_key => Fixtures.identify(target) }, + nil, @connection) end end end @@ -705,12 +706,12 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash) yaml_value.each do |fixture| raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each) - fixture.each do |name, data| + fixture.each do |name, data| unless data raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)" end - self[name] = Fixture.new(data, model_class) + self[name] = Fixture.new(data, model_class, @connection) end end end @@ -723,7 +724,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash) reader.each do |row| data = {} row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip } - self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class) + self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection) end end @@ -761,7 +762,8 @@ class Fixture #:nodoc: attr_reader :model_class - def initialize(fixture, model_class) + def initialize(fixture, model_class, connection = ActiveRecord::Base.connection) + @connection = connection @fixture = fixture @model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil end @@ -783,14 +785,14 @@ class Fixture #:nodoc: end def key_list - columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) } + columns = @fixture.keys.collect{ |column_name| @connection.quote_column_name(column_name) } columns.join(", ") end def value_list list = @fixture.inject([]) do |fixtures, (key, value)| col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base) - fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r") + fixtures << @connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r") end list * ', ' end diff --git a/vendor/rails/activerecord/lib/active_record/i18n_interpolation_deprecation.rb b/vendor/rails/activerecord/lib/active_record/i18n_interpolation_deprecation.rb index cd634e1b..ccb0cac0 100644 --- a/vendor/rails/activerecord/lib/active_record/i18n_interpolation_deprecation.rb +++ b/vendor/rails/activerecord/lib/active_record/i18n_interpolation_deprecation.rb @@ -10,7 +10,7 @@ module I18n protected def interpolate_with_deprecated_syntax(locale, string, values = {}) - return string unless string.is_a?(String) + return string unless string.is_a?(String) && !values.empty? string = string.gsub(/%d|%s/) do |s| instead = DEPRECATED_INTERPOLATORS[s] diff --git a/vendor/rails/activerecord/lib/active_record/locale/en.yml b/vendor/rails/activerecord/lib/active_record/locale/en.yml index bf8a71d2..2813524d 100644 --- a/vendor/rails/activerecord/lib/active_record/locale/en.yml +++ b/vendor/rails/activerecord/lib/active_record/locale/en.yml @@ -23,8 +23,12 @@ en: less_than_or_equal_to: "must be less than or equal to {{count}}" odd: "must be odd" even: "must be even" + record_invalid: "Validation failed: {{errors}}" # Append your own errors here or at the model/attributes scope. + full_messages: + format: "{{attribute}} {{message}}" + # You can define own errors for models or model attributes. # The values :model, :attribute and :value are always available for interpolation. # diff --git a/vendor/rails/activerecord/lib/active_record/named_scope.rb b/vendor/rails/activerecord/lib/active_record/named_scope.rb index 3df70890..0b772241 100644 --- a/vendor/rails/activerecord/lib/active_record/named_scope.rb +++ b/vendor/rails/activerecord/lib/active_record/named_scope.rb @@ -89,12 +89,7 @@ module ActiveRecord when Hash options when Proc - case parent_scope - when Scope - with_scope(:find => parent_scope.proxy_options) { options.call(*args) } - else - options.call(*args) - end + options.call(*args) end, &block) end (class << self; self end).instance_eval do diff --git a/vendor/rails/activerecord/lib/active_record/reflection.rb b/vendor/rails/activerecord/lib/active_record/reflection.rb index 2d4c1d55..54b8c612 100644 --- a/vendor/rails/activerecord/lib/active_record/reflection.rb +++ b/vendor/rails/activerecord/lib/active_record/reflection.rb @@ -297,7 +297,7 @@ module ActiveRecord raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection) end - unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil? + unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil? raise HasManyThroughSourceAssociationMacroError.new(self) end end diff --git a/vendor/rails/activerecord/lib/active_record/schema_dumper.rb b/vendor/rails/activerecord/lib/active_record/schema_dumper.rb index 651cd361..68a45a01 100644 --- a/vendor/rails/activerecord/lib/active_record/schema_dumper.rb +++ b/vendor/rails/activerecord/lib/active_record/schema_dumper.rb @@ -84,7 +84,6 @@ HEADER elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) end - pk ||= 'id' tbl.print " create_table #{table.inspect}" if columns.detect { |c| c.name == pk } @@ -180,4 +179,4 @@ HEADER end end end -end \ No newline at end of file +end diff --git a/vendor/rails/activerecord/lib/active_record/serializers/json_serializer.rb b/vendor/rails/activerecord/lib/active_record/serializers/json_serializer.rb index 085d4f17..a20dfbc3 100644 --- a/vendor/rails/activerecord/lib/active_record/serializers/json_serializer.rb +++ b/vendor/rails/activerecord/lib/active_record/serializers/json_serializer.rb @@ -74,12 +74,14 @@ module ActiveRecord #:nodoc: # {"comments": [{"body": "Don't think too hard"}], # "title": "So I was thinking"}]} def to_json(options = {}) - hash = Serializer.new(self, options).serializable_record - hash = { self.class.model_name.element => hash } if include_root_in_json - ActiveSupport::JSON.encode(hash) + super end - def as_json(options = nil) self end #:nodoc: + def as_json(options = nil) #:nodoc: + hash = Serializer.new(self, options).serializable_record + hash = { self.class.model_name.element => hash } if include_root_in_json + hash + end def from_json(json) self.attributes = ActiveSupport::JSON.decode(json) 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 fa758746..9bf11930 100644 --- a/vendor/rails/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/vendor/rails/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -178,7 +178,7 @@ module ActiveRecord #:nodoc: end def root - root = (options[:root] || @record.class.to_s.underscore).to_s + root = (options[:root] || @record.class.model_name.singular).to_s reformat_name(root) end @@ -320,7 +320,11 @@ module ActiveRecord #:nodoc: protected def compute_type - type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type + type = if @record.class.serialized_attributes.has_key?(name) + :yaml + else + @record.class.columns_hash[name].try(:type) + end case type when :text diff --git a/vendor/rails/activerecord/lib/active_record/validations.rb b/vendor/rails/activerecord/lib/active_record/validations.rb index d2d12b80..b339f0c5 100644 --- a/vendor/rails/activerecord/lib/active_record/validations.rb +++ b/vendor/rails/activerecord/lib/active_record/validations.rb @@ -10,15 +10,122 @@ module ActiveRecord attr_reader :record def initialize(record) @record = record - super("Validation failed: #{@record.errors.full_messages.join(", ")}") + errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', ')) + super(I18n.t('activerecord.errors.messages.record_invalid', :errors => errors)) end end + class Error + attr_accessor :base, :attribute, :type, :message, :options + + def initialize(base, attribute, type = nil, options = {}) + self.base = base + self.attribute = attribute + self.type = type || :invalid + self.options = options + self.message = options.delete(:message) || self.type + end + + def message + generate_message(@message, options.dup) + end + + def full_message + attribute.to_s == 'base' ? message : generate_full_message(message, options.dup) + end + + alias :to_s :message + + def value + @base.respond_to?(attribute) ? @base.send(attribute) : nil + end + + protected + + # Translates an error message in it's default scope (activerecord.errrors.messages). + # Error messages are first looked up in models.MODEL.attributes.ATTRIBUTE.MESSAGE, if it's not there, + # it's looked up in models.MODEL.MESSAGE and if that is not there it returns the translation of the + # default message (e.g. activerecord.errors.messages.MESSAGE). The translated model name, + # translated attribute name and the value are available for interpolation. + # + # When using inheritence in your models, it will check all the inherited models too, but only if the model itself + # hasn't been found. Say you have class Admin < User; end and you wanted the translation for the :blank + # error +message+ for the title +attribute+, it looks for these translations: + # + #
    + #
  1. activerecord.errors.models.admin.attributes.title.blank
  2. + #
  3. activerecord.errors.models.admin.blank
  4. + #
  5. activerecord.errors.models.user.attributes.title.blank
  6. + #
  7. activerecord.errors.models.user.blank
  8. + #
  9. activerecord.errors.messages.blank
  10. + #
  11. any default you provided through the +options+ hash (in the activerecord.errors scope)
  12. + #
+ def generate_message(message, options = {}) + keys = @base.class.self_and_descendants_from_active_record.map do |klass| + [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}", + :"models.#{klass.name.underscore}.#{message}" ] + end.flatten + + keys << options.delete(:default) + keys << :"messages.#{message}" + keys << message if message.is_a?(String) + keys << @type unless @type == message + keys.compact! + + options.reverse_merge! :default => keys, + :scope => [:activerecord, :errors], + :model => @base.class.human_name, + :attribute => @base.class.human_attribute_name(attribute.to_s), + :value => value + + I18n.translate(keys.shift, options) + end + + # Wraps an error message into a full_message format. + # + # The default full_message format for any locale is "{{attribute}} {{message}}". + # One can specify locale specific default full_message format by storing it as a + # translation for the key :"activerecord.errors.full_messages.format". + # + # Additionally one can specify a validation specific error message format by + # storing a translation for :"activerecord.errors.full_messages.[message_key]". + # E.g. the full_message format for any validation that uses :blank as a message + # key (such as validates_presence_of) can be stored to :"activerecord.errors.full_messages.blank". + # + # Because the message key used by a validation can be overwritten on the + # validates_* class macro level one can customize the full_message format for + # any particular validation: + # + # # app/models/article.rb + # class Article < ActiveRecord::Base + # validates_presence_of :title, :message => :"title.blank" + # end + # + # # config/locales/en.yml + # en: + # activerecord: + # errors: + # full_messages: + # title: + # blank: This title is screwed! + def generate_full_message(message, options = {}) + options.reverse_merge! :message => self.message, + :model => @base.class.human_name, + :attribute => @base.class.human_attribute_name(attribute.to_s), + :value => value + + key = :"full_messages.#{@message}" + defaults = [:'full_messages.format', '{{attribute}} {{message}}'] + + I18n.t(key, options.merge(:default => defaults, :scope => [:activerecord, :errors])) + end + end + # Active Record validation is reported to and from this object, which is used by Base#save to # determine whether the object is in a valid state to be saved. See usage example in Validations. class Errors include Enumerable - + class << self def default_error_messages ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages').") @@ -43,11 +150,19 @@ module ActiveRecord # error can be added to the same +attribute+ in which case an array will be returned on a call to on(attribute). # If no +messsage+ is supplied, :invalid is assumed. # If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error). - def add(attribute, message = nil, options = {}) - message ||= :invalid - message = generate_message(attribute, message, options) if message.is_a?(Symbol) + # def add(attribute, message = nil, options = {}) + # message ||= :invalid + # message = generate_message(attribute, message, options)) if message.is_a?(Symbol) + # @errors[attribute.to_s] ||= [] + # @errors[attribute.to_s] << message + # end + + def add(error_or_attr, message = nil, options = {}) + error, attribute = error_or_attr.is_a?(Error) ? [error_or_attr, error_or_attr.attribute] : [nil, error_or_attr] + options[:message] = options.delete(:default) if options.has_key?(:default) + @errors[attribute.to_s] ||= [] - @errors[attribute.to_s] << message + @errors[attribute.to_s] << (error || Error.new(@base, attribute, message, options)) end # Will add an error message to each of the attributes in +attributes+ that is empty. @@ -66,49 +181,6 @@ module ActiveRecord add(attr, :blank, :default => custom_message) if value.blank? end end - - # Translates an error message in it's default scope (activerecord.errrors.messages). - # Error messages are first looked up in models.MODEL.attributes.ATTRIBUTE.MESSAGE, if it's not there, - # it's looked up in models.MODEL.MESSAGE and if that is not there it returns the translation of the - # default message (e.g. activerecord.errors.messages.MESSAGE). The translated model name, - # translated attribute name and the value are available for interpolation. - # - # When using inheritence in your models, it will check all the inherited models too, but only if the model itself - # hasn't been found. Say you have class Admin < User; end and you wanted the translation for the :blank - # error +message+ for the title +attribute+, it looks for these translations: - # - #
    - #
  1. activerecord.errors.models.admin.attributes.title.blank
  2. - #
  3. activerecord.errors.models.admin.blank
  4. - #
  5. activerecord.errors.models.user.attributes.title.blank
  6. - #
  7. activerecord.errors.models.user.blank
  8. - #
  9. activerecord.errors.messages.blank
  10. - #
  11. any default you provided through the +options+ hash (in the activerecord.errors scope)
  12. - #
- def generate_message(attribute, message = :invalid, options = {}) - - message, options[:default] = options[:default], message if options[:default].is_a?(Symbol) - - 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 - - defaults << options.delete(:default) - defaults = defaults.compact.flatten << :"messages.#{message}" - - key = defaults.shift - value = @base.respond_to?(attribute) ? @base.send(attribute) : nil - - options = { :default => defaults, - :model => @base.class.human_name, - :attribute => @base.class.human_attribute_name(attribute.to_s), - :value => value, - :scope => [:activerecord, :errors] - }.merge(options) - - I18n.translate(key, options) - end # Returns true if the specified +attribute+ has errors associated with it. # @@ -138,8 +210,9 @@ module ActiveRecord # company.errors.on(:email) # => "can't be blank" # company.errors.on(:address) # => nil def on(attribute) - errors = @errors[attribute.to_s] - return nil if errors.nil? + attribute = attribute.to_s + return nil unless @errors.has_key?(attribute) + errors = @errors[attribute].map(&:to_s) errors.size == 1 ? errors.first : errors end @@ -163,7 +236,11 @@ module ActiveRecord # # name - can't be blank # # address - can't be blank def each - @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } } + @errors.each_key { |attr| @errors[attr].each { |error| yield attr, error.message } } + end + + def each_error + @errors.each_key { |attr| @errors[attr].each { |error| yield attr, error } } end # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned @@ -194,22 +271,10 @@ module ActiveRecord # company.errors.full_messages # => # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"] def full_messages(options = {}) - full_messages = [] - - @errors.each_key do |attr| - @errors[attr].each do |message| - next unless message - - if attr == "base" - full_messages << message - else - attr_name = @base.class.human_attribute_name(attr) - full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message - end - end + @errors.values.inject([]) do |full_messages, errors| + full_messages + errors.map { |error| error.full_message } end - full_messages - end + end # Returns true if no errors have been added. def empty? @@ -254,7 +319,11 @@ module ActiveRecord full_messages.each { |msg| e.error(msg) } end end - + + def generate_message(attribute, message = :invalid, options = {}) + ActiveSupport::Deprecation.warn("ActiveRecord::Errors#generate_message has been deprecated. Please use ActiveRecord::Error#generate_message.") + Error.new(@base, attribute, message, options).to_s + end end @@ -437,7 +506,7 @@ module ActiveRecord validates_each(attr_names, configuration) do |record, attr_name, value| unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation") - record.errors.add(attr_name, :confirmation, :default => configuration[:message]) + record.errors.add(attr_name, :confirmation, :default => configuration[:message]) end end end @@ -479,7 +548,7 @@ module ActiveRecord validates_each(attr_names,configuration) do |record, attr_name, value| unless value == configuration[:accept] - record.errors.add(attr_name, :accepted, :default => configuration[:message]) + record.errors.add(attr_name, :accepted, :default => configuration[:message]) end end end @@ -499,7 +568,7 @@ module ActiveRecord # # Configuration options: # * message - A custom error message (default is: "can't be blank"). - # * on - Specifies when this validation is active (default is :save, other options :create, + # * on - Specifies when this validation is active (default is :save, other options :create, # :update). # * if - Specifies a method, proc or string to call to determine if the validation should # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). @@ -599,7 +668,7 @@ module ActiveRecord validates_each(attrs, options) do |record, attr, value| value = options[:tokenizer].call(value) if value.kind_of?(String) unless !value.nil? and value.size.method(validity_checks[option])[option_value] - record.errors.add(attr, key, :default => custom_message, :count => option_value) + record.errors.add(attr, key, :default => custom_message, :count => option_value) end end end @@ -687,7 +756,7 @@ module ActiveRecord # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the # rare case that a race condition occurs, the database will guarantee # the field's uniqueness. - # + # # When the database catches such a duplicate insertion, # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid # exception. You can either choose to let this error propagate (which @@ -696,7 +765,7 @@ module ActiveRecord # that the title already exists, and asking him to re-enter the title). # This technique is also known as optimistic concurrency control: # http://en.wikipedia.org/wiki/Optimistic_concurrency_control - # + # # Active Record currently provides no way to distinguish unique # index constraint errors from other types of database errors, so you # will have to parse the (database-specific) exception message to detect @@ -726,7 +795,7 @@ module ActiveRecord comparison_operator = "IS ?" elsif column.text? comparison_operator = "#{connection.case_sensitive_equality_operator} ?" - value = column.limit ? value.to_s[0, column.limit] : value.to_s + value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s else comparison_operator = "= ?" end @@ -794,7 +863,7 @@ module ActiveRecord validates_each(attr_names, configuration) do |record, attr_name, value| unless value.to_s =~ configuration[:with] - record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) + record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) end end end @@ -828,7 +897,7 @@ module ActiveRecord validates_each(attr_names, configuration) do |record, attr_name, value| unless enum.include?(value) - record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value) + record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value) end end end @@ -862,7 +931,7 @@ module ActiveRecord validates_each(attr_names, configuration) do |record, attr_name, value| if enum.include?(value) - record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value) + record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value) end end end @@ -970,7 +1039,7 @@ module ActiveRecord case option when :odd, :even unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[] - record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message]) + record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message]) end else record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option]) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]] diff --git a/vendor/rails/activerecord/lib/active_record/version.rb b/vendor/rails/activerecord/lib/active_record/version.rb index cbd6958a..7d75ee6e 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 = 3 + TINY = 4 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/activerecord/test/cases/adapter_test.rb b/vendor/rails/activerecord/test/cases/adapter_test.rb index 04770646..3dd3dd8f 100644 --- a/vendor/rails/activerecord/test/cases/adapter_test.rb +++ b/vendor/rails/activerecord/test/cases/adapter_test.rb @@ -63,6 +63,18 @@ class AdapterTest < ActiveRecord::TestCase def test_show_nonexistent_variable_returns_nil assert_nil @connection.show_variable('foo_bar_baz') end + + def test_not_specifying_database_name_for_cross_database_selects + assert_nothing_raised do + ActiveRecord::Base.establish_connection({ + :adapter => 'mysql', + :username => 'rails' + }) + ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses" + end + + ActiveRecord::Base.establish_connection 'arunit' + end end if current_adapter?(:PostgreSQLAdapter) 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 970601cd..9f3945f8 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 @@ -249,24 +249,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 1, Topic.find(topic.id)[:replies_count] end - def test_belongs_to_counter_after_save - topic = Topic.create("title" => "monday night") - topic.replies.create("title" => "re: monday night", "content" => "football") - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - - topic.save - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - end - - def test_belongs_to_counter_after_update_attributes - topic = Topic.create("title" => "37s") - topic.replies.create("title" => "re: 37s", "content" => "rails") - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - - topic.update_attributes("title" => "37signals") - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - end - def test_assignment_before_child_saved final_cut = Client.new("name" => "Final Cut") firm = Firm.find(1) 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 1b2e0fc1..677226ec 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,6 +1,6 @@ require 'cases/helper' -require 'models/author' require 'models/post' +require 'models/author' require 'models/comment' require 'models/category' require 'models/categorization' @@ -66,13 +66,13 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase def setup generate_test_object_graphs end - + def teardown - [Circle, Square, Triangle, PaintColor, PaintTexture, + [Circle, Square, Triangle, PaintColor, PaintTexture, ShapeExpression, NonPolyOne, NonPolyTwo].each do |c| c.delete_all end - + end @@ -127,4 +127,4 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase Author.all :include => includes, :conditions => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name' end end -end \ No newline at end of file +end diff --git a/vendor/rails/activerecord/test/cases/associations/habtm_join_table_test.rb b/vendor/rails/activerecord/test/cases/associations/habtm_join_table_test.rb new file mode 100644 index 00000000..bf3e04c3 --- /dev/null +++ b/vendor/rails/activerecord/test/cases/associations/habtm_join_table_test.rb @@ -0,0 +1,56 @@ +require 'cases/helper' + +class MyReader < ActiveRecord::Base + has_and_belongs_to_many :my_books +end + +class MyBook < ActiveRecord::Base + has_and_belongs_to_many :my_readers +end + +class HabtmJoinTableTest < ActiveRecord::TestCase + def setup + ActiveRecord::Base.connection.create_table :my_books, :force => true do |t| + t.string :name + end + assert ActiveRecord::Base.connection.table_exists?(:my_books) + + ActiveRecord::Base.connection.create_table :my_readers, :force => true do |t| + t.string :name + end + assert ActiveRecord::Base.connection.table_exists?(:my_readers) + + ActiveRecord::Base.connection.create_table :my_books_my_readers, :force => true do |t| + t.integer :my_book_id + t.integer :my_reader_id + end + assert ActiveRecord::Base.connection.table_exists?(:my_books_my_readers) + end + + def teardown + ActiveRecord::Base.connection.drop_table :my_books + ActiveRecord::Base.connection.drop_table :my_readers + ActiveRecord::Base.connection.drop_table :my_books_my_readers + end + + uses_transaction :test_should_raise_exception_when_join_table_has_a_primary_key + def test_should_raise_exception_when_join_table_has_a_primary_key + if ActiveRecord::Base.connection.supports_primary_key? + assert_raise ActiveRecord::ConfigurationError do + jaime = MyReader.create(:name=>"Jaime") + jaime.my_books << MyBook.create(:name=>'Great Expectations') + end + end + end + + uses_transaction :test_should_cache_result_of_primary_key_check + def test_should_cache_result_of_primary_key_check + if ActiveRecord::Base.connection.supports_primary_key? + ActiveRecord::Base.connection.stubs(:primary_key).with('my_books_my_readers').returns(false).once + weaz = MyReader.create(:name=>'Weaz') + + weaz.my_books << MyBook.create(:name=>'Great Expectations') + weaz.my_books << MyBook.create(:name=>'Greater Expectations') + end + end +end 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 5df74fcd..23a10711 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 @@ -10,11 +10,12 @@ require 'models/author' require 'models/comment' require 'models/person' require 'models/reader' +require 'models/tagging' class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, :author_addresses, - :people, :posts, :readers + :people, :posts, :readers, :taggings def setup Client.destroyed_client_ids.clear @@ -279,6 +280,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }]) end + def test_find_all_with_include_and_conditions + assert_nothing_raised do + Developer.find(:all, :joins => :audit_logs, :conditions => {'audit_logs.message' => nil, :name => 'Smith'}) + end + end + def test_find_in_collection assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name assert_raise(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) } @@ -502,6 +509,23 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, new_firm.clients_of_firm.size end + def test_deleting_updates_counter_cache + topic = Topic.first + assert_equal topic.replies.to_a.size, topic.replies_count + + topic.replies.delete(topic.replies.first) + topic.reload + assert_equal topic.replies.to_a.size, topic.replies_count + end + + def test_deleting_updates_counter_cache_without_dependent_destroy + post = posts(:welcome) + + assert_difference "post.reload.taggings_count", -1 do + post.taggings.delete(post.taggings.first) + end + end + def test_deleting_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") @@ -547,6 +571,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end + def test_clearing_updates_counter_cache + topic = Topic.first + + topic.replies.clear + topic.reload + assert_equal 0, topic.replies_count + end + def test_clearing_a_dependent_association_collection firm = companies(:first_firm) client_id = firm.dependent_clients_of_firm.first.id @@ -691,6 +723,28 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, companies(:first_firm).clients_of_firm(true).size end + def test_destroying_by_fixnum_id + 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.id) + 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_by_string_id + 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.id.to_s) + 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") @@ -861,7 +915,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase lambda { authors(:mary).comments = [comments(:greetings), comments(:more_greetings)] }, lambda { authors(:mary).comments << Comment.create!(:body => "Yay", :post_id => 424242) }, lambda { authors(:mary).comments.delete(authors(:mary).comments.first) }, - ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection, &block) } + ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } end def test_dynamic_find_should_respect_association_order_for_through 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 97efca78..a43f876a 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 @@ -11,9 +11,13 @@ require 'models/author' require 'models/owner' require 'models/pet' require 'models/toy' +require 'models/contract' +require 'models/company' +require 'models/developer' class HasManyThroughAssociationsTest < ActiveRecord::TestCase - fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys + fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, + :companies def test_associate_existing assert_queries(2) { posts(:thinking);people(:david) } @@ -157,6 +161,30 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal peeps + 1, posts(:thinking).people.count end + def test_associate_with_create_and_invalid_options + peeps = companies(:first_firm).developers.count + assert_nothing_raised { companies(:first_firm).developers.create(:name => '0') } + assert_equal peeps, companies(:first_firm).developers.count + end + + def test_associate_with_create_and_valid_options + peeps = companies(:first_firm).developers.count + assert_nothing_raised { companies(:first_firm).developers.create(:name => 'developer') } + assert_equal peeps + 1, companies(:first_firm).developers.count + end + + def test_associate_with_create_bang_and_invalid_options + peeps = companies(:first_firm).developers.count + assert_raises(ActiveRecord::RecordInvalid) { companies(:first_firm).developers.create!(:name => '0') } + assert_equal peeps, companies(:first_firm).developers.count + end + + def test_associate_with_create_bang_and_valid_options + peeps = companies(:first_firm).developers.count + assert_nothing_raised { companies(:first_firm).developers.create!(:name => 'developer') } + assert_equal peeps + 1, companies(:first_firm).developers.count + end + def test_clear_associations assert_queries(2) { posts(:welcome);posts(:welcome).people(true) } @@ -276,4 +304,21 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys assert_equal 1, owners(:blackbeard).toys.count end + + def test_find_on_has_many_association_collection_with_include_and_conditions + post_with_no_comments = people(:michael).posts_with_no_comments.first + assert_equal post_with_no_comments, posts(:authorless) + end + + def test_has_many_through_has_one_reflection + assert_equal [comments(:eager_sti_on_associations_vs_comment)], authors(:david).very_special_comments + end + + def test_modifying_has_many_through_has_one_reflection_should_raise + [ + lambda { authors(:david).very_special_comments = [VerySpecialComment.create!(:body => "Gorp!", :post_id => 1011), VerySpecialComment.create!(:body => "Eep!", :post_id => 1012)] }, + lambda { authors(:david).very_special_comments << VerySpecialComment.create!(:body => "Hoohah!", :post_id => 1013) }, + lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) }, + ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } + 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 ab6e6d20..9aef3eb3 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 @@ -28,6 +28,16 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase assert_not_nil new_member.current_membership assert_not_nil new_member.club end + + def test_creating_association_builds_through_record_for_new + new_member = Member.new(:name => "Jane") + new_member.club = clubs(:moustache_club) + assert new_member.current_membership + assert_equal clubs(:moustache_club), new_member.current_membership.club + assert_equal clubs(:moustache_club), new_member.club + assert new_member.save + assert_equal clubs(:moustache_club), new_member.club + end def test_replace_target_record new_club = Club.create(:name => "Marx Bros") diff --git a/vendor/rails/activerecord/test/cases/associations/join_model_test.rb b/vendor/rails/activerecord/test/cases/associations/join_model_test.rb index b1060d01..21412d1e 100644 --- a/vendor/rails/activerecord/test/cases/associations/join_model_test.rb +++ b/vendor/rails/activerecord/test/cases/associations/join_model_test.rb @@ -317,11 +317,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_belongs_to_polymorphic_with_counter_cache - assert_equal 0, posts(:welcome)[:taggings_count] + assert_equal 1, posts(:welcome)[:taggings_count] tagging = posts(:welcome).taggings.create(:tag => tags(:general)) - assert_equal 1, posts(:welcome, :reload)[:taggings_count] + assert_equal 2, posts(:welcome, :reload)[:taggings_count] tagging.destroy - assert posts(:welcome, :reload)[:taggings_count].zero? + assert_equal 1, posts(:welcome, :reload)[:taggings_count] end def test_unavailable_through_reflection @@ -377,7 +377,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_polymorphic_has_one - assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tagging } + assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).tagging end def test_has_many_through_polymorphic_has_many diff --git a/vendor/rails/activerecord/test/cases/base_test.rb b/vendor/rails/activerecord/test/cases/base_test.rb index 604271e8..4530eecb 100755 --- a/vendor/rails/activerecord/test/cases/base_test.rb +++ b/vendor/rails/activerecord/test/cases/base_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'models/post' require 'models/author' require 'models/topic' require 'models/reply' @@ -12,7 +13,6 @@ require 'models/auto_id' require 'models/column_name' require 'models/subscriber' require 'models/keyboard' -require 'models/post' require 'models/comment' require 'models/minimalistic' require 'models/warehouse_thing' @@ -1012,7 +1012,25 @@ class BasicsTest < ActiveRecord::TestCase assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date end - def test_multiparameter_attributes_on_date_with_empty_date + def test_multiparameter_attributes_on_date_with_empty_year + attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(1, 6, 24), topic.last_read.to_date + end + + def test_multiparameter_attributes_on_date_with_empty_month + attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(2004, 1, 24), topic.last_read.to_date + end + + def test_multiparameter_attributes_on_date_with_empty_day attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" } topic = Topic.find(1) topic.attributes = attributes @@ -1021,6 +1039,33 @@ class BasicsTest < ActiveRecord::TestCase assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date end + def test_multiparameter_attributes_on_date_with_empty_day_and_year + attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(1, 6, 1), topic.last_read.to_date + end + + def test_multiparameter_attributes_on_date_with_empty_day_and_month + attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(2004, 1, 1), topic.last_read.to_date + end + + def test_multiparameter_attributes_on_date_with_empty_year_and_month + attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(1, 1, 24), topic.last_read.to_date + end + def test_multiparameter_attributes_on_date_with_all_empty attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" } topic = Topic.find(1) @@ -1115,7 +1160,7 @@ class BasicsTest < ActiveRecord::TestCase Time.zone = nil Topic.skip_time_zone_conversion_for_attributes = [] end - + def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.default_timezone = :utc @@ -1439,7 +1484,7 @@ class BasicsTest < ActiveRecord::TestCase topic = Topic.create("content" => myobj).reload assert_equal(myobj, topic.content) end - + def test_serialized_string_attribute myobj = "Yes" topic = Topic.create("content" => myobj).reload diff --git a/vendor/rails/activerecord/test/cases/calculations_test.rb b/vendor/rails/activerecord/test/cases/calculations_test.rb index b4f76cbc..f4280524 100644 --- a/vendor/rails/activerecord/test/cases/calculations_test.rb +++ b/vendor/rails/activerecord/test/cases/calculations_test.rb @@ -5,6 +5,8 @@ require 'models/edge' require 'models/owner' require 'models/pet' require 'models/toy' +require 'models/club' +require 'models/organization' Company.has_many :accounts @@ -226,6 +228,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 15, companies(:rails_core).companies.sum(:id) end + def test_should_sum_scoped_field_with_from + assert_equal Club.count, Organization.clubs.count + end + def test_should_sum_scoped_field_with_conditions assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7') end diff --git a/vendor/rails/activerecord/test/cases/column_definition_test.rb b/vendor/rails/activerecord/test/cases/column_definition_test.rb index 98abc8ea..fc9a0ac9 100644 --- a/vendor/rails/activerecord/test/cases/column_definition_test.rb +++ b/vendor/rails/activerecord/test/cases/column_definition_test.rb @@ -33,4 +33,38 @@ class ColumnDefinitionTest < ActiveRecord::TestCase column.limit, column.precision, column.scale, column.default, column.null) assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, column_def.to_sql end + + if current_adapter?(:MysqlAdapter) + def test_should_set_default_for_mysql_binary_data_types + binary_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "binary(1)") + assert_equal "a", binary_column.default + + varbinary_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "varbinary(1)") + assert_equal "a", varbinary_column.default + end + + def test_should_not_set_default_for_blob_and_text_data_types + assert_raise ArgumentError do + ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "blob") + end + + assert_raise ArgumentError do + ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "Hello", "text") + end + + text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text") + assert_equal nil, text_column.default + + not_null_text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text", false) + assert_equal "", not_null_text_column.default + end + + def test_has_default_should_return_false_for_blog_and_test_data_types + blob_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "blob") + assert !blob_column.has_default? + + text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text") + assert !text_column.has_default? + end + end end diff --git a/vendor/rails/activerecord/test/cases/dirty_test.rb b/vendor/rails/activerecord/test/cases/dirty_test.rb index ac95bac4..fb3d26f6 100644 --- a/vendor/rails/activerecord/test/cases/dirty_test.rb +++ b/vendor/rails/activerecord/test/cases/dirty_test.rb @@ -288,6 +288,16 @@ class DirtyTest < ActiveRecord::TestCase end end + def test_save_should_not_save_serialized_attribute_with_partial_updates_if_not_present + with_partial_updates(Topic) do + Topic.create!(:author_name => 'Bill', :content => {:a => "a"}) + topic = Topic.first(:select => 'id, author_name') + topic.update_attribute :author_name, 'John' + topic = Topic.first + assert_not_nil topic.content + end + end + private def with_partial_updates(klass, on = true) old = klass.partial_updates? diff --git a/vendor/rails/activerecord/test/cases/finder_test.rb b/vendor/rails/activerecord/test/cases/finder_test.rb index 25e339f5..f1bede9b 100644 --- a/vendor/rails/activerecord/test/cases/finder_test.rb +++ b/vendor/rails/activerecord/test/cases/finder_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'models/post' require 'models/author' require 'models/categorization' require 'models/comment' @@ -7,7 +8,6 @@ require 'models/topic' require 'models/reply' require 'models/entrant' require 'models/developer' -require 'models/post' require 'models/customer' require 'models/job' require 'models/categorization' @@ -94,16 +94,16 @@ class FinderTest < ActiveRecord::TestCase assert_raise(NoMethodError) { Topic.exists?([1,2]) } end - + def test_exists_returns_true_with_one_record_and_no_args assert Topic.exists? end - + def test_does_not_exist_with_empty_table_and_no_args_given Topic.delete_all assert !Topic.exists? end - + def test_exists_with_aggregate_having_three_mappings existing_address = customers(:david).address assert Customer.exists?(:address => existing_address) @@ -156,10 +156,8 @@ class FinderTest < ActiveRecord::TestCase end def test_find_all_with_limit - entrants = Entrant.find(:all, :order => "id ASC", :limit => 2) - - assert_equal(2, entrants.size) - assert_equal(entrants(:first).name, entrants.first.name) + assert_equal(2, Entrant.find(:all, :limit => 2).size) + assert_equal(0, Entrant.find(:all, :limit => 0).size) end def test_find_all_with_prepared_limit_and_offset @@ -168,21 +166,22 @@ class FinderTest < ActiveRecord::TestCase assert_equal(2, entrants.size) assert_equal(entrants(:second).name, entrants.first.name) + assert_equal 3, Entrant.count entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2) assert_equal(1, entrants.size) assert_equal(entrants(:third).name, entrants.first.name) end - def test_find_all_with_limit_and_offset_and_multiple_orderings - developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1) - assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name} + def test_find_all_with_limit_and_offset_and_multiple_order_clauses + first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0 + second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3 + last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 + + assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] } + assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] } + assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] } end - def test_find_with_limit_and_condition - developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7) - assert_equal(1, developers.size) - assert_equal("fixture_3", developers.first.name) - end def test_find_with_group developers = Developer.find(:all, :group => "salary", :select => "salary") @@ -941,40 +940,6 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } end - def test_find_all_with_limit - first_five_developers = Developer.find :all, :order => 'id ASC', :limit => 5 - assert_equal 5, first_five_developers.length - assert_equal 'David', first_five_developers.first.name - assert_equal 'fixture_5', first_five_developers.last.name - - no_developers = Developer.find :all, :order => 'id ASC', :limit => 0 - assert_equal 0, no_developers.length - end - - def test_find_all_with_limit_and_offset - first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0 - second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3 - last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8 - - assert_equal 3, first_three_developers.length - assert_equal 3, second_three_developers.length - assert_equal 2, last_two_developers.length - - assert_equal 'David', first_three_developers.first.name - assert_equal 'fixture_4', second_three_developers.first.name - assert_equal 'fixture_9', last_two_developers.first.name - end - - def test_find_all_with_limit_and_offset_and_multiple_order_clauses - first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0 - second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3 - last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 - - assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] } - end - def test_find_all_with_join developers_on_project_one = Developer.find( :all, diff --git a/vendor/rails/activerecord/test/cases/fixtures_test.rb b/vendor/rails/activerecord/test/cases/fixtures_test.rb index b07d4f35..eb3f03c9 100644 --- a/vendor/rails/activerecord/test/cases/fixtures_test.rb +++ b/vendor/rails/activerecord/test/cases/fixtures_test.rb @@ -185,7 +185,7 @@ class FixturesTest < ActiveRecord::TestCase def test_binary_in_fixtures assert_equal 1, @binaries.size - data = File.read(ASSETS_ROOT + "/flowers.jpg") + data = File.open(ASSETS_ROOT + "/flowers.jpg", 'rb') { |f| f.read } data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) data.freeze assert_equal data, @flowers.data diff --git a/vendor/rails/activerecord/test/cases/i18n_test.rb b/vendor/rails/activerecord/test/cases/i18n_test.rb index b1db662e..d59c53ce 100644 --- a/vendor/rails/activerecord/test/cases/i18n_test.rb +++ b/vendor/rails/activerecord/test/cases/i18n_test.rb @@ -12,6 +12,11 @@ class ActiveRecordI18nTests < Test::Unit::TestCase I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } } assert_equal 'topic title attribute', Topic.human_attribute_name('title') end + + def test_translated_model_attributes_with_symbols + I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } } + assert_equal 'topic title attribute', Topic.human_attribute_name(:title) + end def test_translated_model_attributes_with_sti I18n.backend.store_translations 'en', :activerecord => {:attributes => {:reply => {:title => 'reply title attribute'} } } diff --git a/vendor/rails/activerecord/test/cases/method_scoping_test.rb b/vendor/rails/activerecord/test/cases/method_scoping_test.rb index 2f660a3e..d8246f49 100644 --- a/vendor/rails/activerecord/test/cases/method_scoping_test.rb +++ b/vendor/rails/activerecord/test/cases/method_scoping_test.rb @@ -1,9 +1,9 @@ require "cases/helper" +require 'models/post' require 'models/author' require 'models/developer' require 'models/project' require 'models/comment' -require 'models/post' require 'models/category' class MethodScopingTest < ActiveRecord::TestCase diff --git a/vendor/rails/activerecord/test/cases/migration_test.rb b/vendor/rails/activerecord/test/cases/migration_test.rb index 16861f21..3b6326db 100644 --- a/vendor/rails/activerecord/test/cases/migration_test.rb +++ b/vendor/rails/activerecord/test/cases/migration_test.rb @@ -25,6 +25,24 @@ if ActiveRecord::Base.connection.supports_migrations? end end + class MigrationTableAndIndexTest < ActiveRecord::TestCase + def test_add_schema_info_respects_prefix_and_suffix + conn = ActiveRecord::Base.connection + + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + ActiveRecord::Base.table_name_prefix = 'foo_' + ActiveRecord::Base.table_name_suffix = '_bar' + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + + conn.initialize_schema_migrations_table + + assert_equal "foo_unique_schema_migrations_bar", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name] + ensure + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + end + end + class MigrationTest < ActiveRecord::TestCase self.use_transactional_fixtures = false @@ -224,7 +242,7 @@ if ActiveRecord::Base.connection.supports_migrations? t.column :foo, :string end - assert_equal %w(foo testings_id), Person.connection.columns(:testings).map { |c| c.name }.sort + assert_equal %w(foo testing_id), Person.connection.columns(:testings).map { |c| c.name }.sort ensure Person.connection.drop_table :testings rescue nil ActiveRecord::Base.primary_key_prefix_type = nil @@ -237,7 +255,7 @@ if ActiveRecord::Base.connection.supports_migrations? t.column :foo, :string end - assert_equal %w(foo testingsid), Person.connection.columns(:testings).map { |c| c.name }.sort + assert_equal %w(foo testingid), Person.connection.columns(:testings).map { |c| c.name }.sort ensure Person.connection.drop_table :testings rescue nil ActiveRecord::Base.primary_key_prefix_type = nil @@ -389,7 +407,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal 9, wealth_column.precision assert_equal 7, wealth_column.scale end - + def test_native_types Person.delete_all Person.connection.add_column "people", "last_name", :string @@ -921,9 +939,9 @@ if ActiveRecord::Base.connection.supports_migrations? def test_migrator_one_down ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid") - + ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1) - + Person.reset_column_information assert Person.column_methods_hash.include?(:last_name) assert !Reminder.table_exists? @@ -1059,20 +1077,20 @@ if ActiveRecord::Base.connection.supports_migrations? assert Reminder.create("content" => "hello world", "remind_at" => Time.now) assert_equal "hello world", Reminder.find(:first).content end - + def test_migrator_rollback ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid") assert_equal(3, ActiveRecord::Migrator.current_version) - + ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") assert_equal(2, ActiveRecord::Migrator.current_version) - + ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") assert_equal(1, ActiveRecord::Migrator.current_version) - + ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") assert_equal(0, ActiveRecord::Migrator.current_version) - + ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") assert_equal(0, ActiveRecord::Migrator.current_version) end @@ -1224,7 +1242,7 @@ if ActiveRecord::Base.connection.supports_migrations? end end - + class SexyMigrationsTest < ActiveRecord::TestCase def test_references_column_type_adds_id with_new_table do |t| @@ -1280,6 +1298,15 @@ if ActiveRecord::Base.connection.supports_migrations? end end + if current_adapter?(:PostgreSQLAdapter) + def test_xml_creates_xml_column + with_new_table do |t| + t.expects(:column).with(:data, 'xml', {}) + t.xml :data + end + end + end + protected def with_new_table Person.connection.create_table :delete_me, :force => true do |t| @@ -1493,3 +1520,4 @@ if ActiveRecord::Base.connection.supports_migrations? end end end + diff --git a/vendor/rails/activerecord/test/cases/modules_test.rb b/vendor/rails/activerecord/test/cases/modules_test.rb index 283333fc..4f559bca 100644 --- a/vendor/rails/activerecord/test/cases/modules_test.rb +++ b/vendor/rails/activerecord/test/cases/modules_test.rb @@ -4,6 +4,23 @@ require 'models/company_in_module' class ModulesTest < ActiveRecord::TestCase fixtures :accounts, :companies, :projects, :developers + def setup + # need to make sure Object::Firm and Object::Client are not defined, + # so that constantize will not be able to cheat when having to load namespaced classes + @undefined_consts = {} + + [:Firm, :Client].each do |const| + @undefined_consts.merge! const => Object.send(:remove_const, const) if Object.const_defined?(const) + end + end + + def teardown + # reinstate the constants that we undefined in the setup + @undefined_consts.each do |constant, value| + Object.send :const_set, constant, value unless value.nil? + end + end + def test_module_spanning_associations firm = MyApplication::Business::Firm.find(:first) assert !firm.clients.empty?, "Firm should have clients" @@ -36,4 +53,29 @@ class ModulesTest < ActiveRecord::TestCase assert_equal 'companies', MyApplication::Business::Client.table_name, 'table_name for ActiveRecord model subclass' assert_equal 'company_contacts', MyApplication::Business::Client::Contact.table_name, 'table_name for ActiveRecord model enclosed by another ActiveRecord model' end + + def test_assign_ids + firm = MyApplication::Business::Firm.first + + assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do + firm.client_ids = [MyApplication::Business::Client.first.id] + end + end + + # need to add an eager loading condition to force the eager loading model into + # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640 + def test_eager_loading_in_modules + clients = [] + + assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do + clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') + clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}) + end + + clients.each do |client| + assert_no_queries do + assert_not_nil(client.firm.account) + end + end + 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 ae6a54a5..208a2eea 100644 --- a/vendor/rails/activerecord/test/cases/named_scope_test.rb +++ b/vendor/rails/activerecord/test/cases/named_scope_test.rb @@ -319,10 +319,6 @@ class NamedScopeTest < ActiveRecord::TestCase 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 @@ -336,6 +332,12 @@ class NamedScopeTest < ActiveRecord::TestCase end end end + + def test_table_names_for_chaining_scopes_with_and_without_table_name_included + assert_nothing_raised do + Comment.for_first_post.for_first_author.all + end + end end class DynamicScopeMatchTest < ActiveRecord::TestCase diff --git a/vendor/rails/activerecord/test/cases/pk_test.rb b/vendor/rails/activerecord/test/cases/pk_test.rb index 948a570b..c121e0aa 100644 --- a/vendor/rails/activerecord/test/cases/pk_test.rb +++ b/vendor/rails/activerecord/test/cases/pk_test.rb @@ -98,4 +98,22 @@ class PrimaryKeysTest < ActiveRecord::TestCase def test_instance_destroy_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find(1).destroy } end + + def test_supports_primary_key + assert_nothing_raised NoMethodError do + ActiveRecord::Base.connection.supports_primary_key? + end + end + + def test_primary_key_returns_value_if_it_exists + if ActiveRecord::Base.connection.supports_primary_key? + assert_equal 'id', ActiveRecord::Base.connection.primary_key('developers') + end + end + + def test_primary_key_returns_nil_if_it_does_not_exist + if ActiveRecord::Base.connection.supports_primary_key? + assert_nil ActiveRecord::Base.connection.primary_key('developers_projects') + end + end end diff --git a/vendor/rails/activerecord/test/cases/reflection_test.rb b/vendor/rails/activerecord/test/cases/reflection_test.rb index 30ec157d..a0107d23 100644 --- a/vendor/rails/activerecord/test/cases/reflection_test.rb +++ b/vendor/rails/activerecord/test/cases/reflection_test.rb @@ -170,8 +170,8 @@ class ReflectionTest < ActiveRecord::TestCase def test_reflection_of_all_associations # FIXME these assertions bust a lot - assert_equal 28, Firm.reflect_on_all_associations.size - assert_equal 21, Firm.reflect_on_all_associations(:has_many).size + assert_equal 30, Firm.reflect_on_all_associations.size + assert_equal 23, 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/schema_dumper_test.rb b/vendor/rails/activerecord/test/cases/schema_dumper_test.rb index 972700df..ba714a95 100644 --- a/vendor/rails/activerecord/test/cases/schema_dumper_test.rb +++ b/vendor/rails/activerecord/test/cases/schema_dumper_test.rb @@ -156,7 +156,7 @@ class SchemaDumperTest < ActiveRecord::TestCase index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip assert_equal 'add_index "companies", ["firm_id", "type", "rating", "ruby_type"], :name => "company_index"', index_definition end - + def test_schema_dump_should_honor_nonstandard_primary_keys output = standard_dump match = output.match(%r{create_table "movies"(.*)do}) @@ -190,4 +190,22 @@ class SchemaDumperTest < ActiveRecord::TestCase output = stream.string assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output end + + if current_adapter?(:PostgreSQLAdapter) + def test_schema_dump_includes_xml_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_xml_data_type"} =~ output + assert_match %r{t.xml "data"}, output + end + end + end + + def test_schema_dump_keeps_id_column_when_id_is_false_and_id_column_added + output = standard_dump + match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n}) + assert_not_nil(match, "goofy_string_id table not found") + assert_match %r(:id => false), match[1], "no table id not preserved" + assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved" + end end + diff --git a/vendor/rails/activerecord/test/cases/validations_i18n_test.rb b/vendor/rails/activerecord/test/cases/validations_i18n_test.rb index 66982346..36eadd55 100644 --- a/vendor/rails/activerecord/test/cases/validations_i18n_test.rb +++ b/vendor/rails/activerecord/test/cases/validations_i18n_test.rb @@ -1,11 +1,83 @@ require "cases/helper" require 'models/topic' require 'models/reply' +require 'models/person' + +module ActiveRecordValidationsI18nTestHelper + def store_translations(*args) + data = args.extract_options! + locale = args.shift || 'en' + I18n.backend.send(:init_translations) + I18n.backend.store_translations(locale, :activerecord => data) + end + + def delete_translation(key) + I18n.backend.instance_eval do + keys = I18n.send(:normalize_translation_keys, 'en', key, nil) + keys.inject(translations) { |result, k| keys.last == k ? result.delete(k.to_sym) : result[k.to_sym] } + end + end + + def reset_callbacks(*models) + models.each do |model| + model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + end + end +end + +# DEPRECATIONS + +class ActiveRecordValidationsI18nDeprecationsTests < ActiveSupport::TestCase + test "default_error_messages is deprecated and can be removed in Rails 3 / ActiveModel" do + assert_deprecated('ActiveRecord::Errors.default_error_messages') do + ActiveRecord::Errors.default_error_messages + end + end + + test "%s interpolation syntax in error messages still works" do + ActiveSupport::Deprecation.silence do + result = I18n.t :does_not_exist, :default => "%s interpolation syntax is deprecated", :value => 'this' + assert_equal result, "this interpolation syntax is deprecated" + end + end + + test "%s interpolation syntax in error messages is deprecated" do + assert_deprecated('using %s in messages') do + I18n.t :does_not_exist, :default => "%s interpolation syntax is deprected", :value => 'this' + end + end + + test "%d interpolation syntax in error messages still works" do + ActiveSupport::Deprecation.silence do + result = I18n.t :does_not_exist, :default => "%d interpolation syntaxes are deprecated", :count => 2 + assert_equal result, "2 interpolation syntaxes are deprecated" + end + end + + test "%d interpolation syntax in error messages is deprecated" do + assert_deprecated('using %d in messages') do + I18n.t :does_not_exist, :default => "%d interpolation syntaxes are deprected", :count => 2 + end + end +end + + +# ACTIVERECORD VALIDATIONS +# +# For each validation: +# +# * test expect that it adds an error with the appropriate arguments +# * test that it looks up the correct default message class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase + include ActiveRecordValidationsI18nTestHelper + def setup - reset_callbacks Topic + reset_callbacks(Topic) @topic = Topic.new + @reply = Reply.new @old_load_path, @old_backend = I18n.load_path, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new @@ -13,13 +85,40 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase end def teardown - reset_callbacks Topic - I18n.load_path.replace @old_load_path + reset_callbacks(Topic) + I18n.load_path.replace(@old_load_path) I18n.backend = @old_backend end + def expect_error_added(model, attribute, type, options) + model.errors.expects(:add).with(attribute, type, options) + yield + model.valid? + end + + def assert_message_translations(model, attribute, type, &block) + assert_default_message_translation(model, attribute, type, &block) + reset_callbacks(model.class) + model.errors.clear + assert_custom_message_translation(model, attribute, type, &block) + end + + def assert_custom_message_translation(model, attribute, type) + store_translations(:errors => { :models => { model.class.name.underscore => { :attributes => { attribute => { type => 'custom message' } } } } }) + yield + model.valid? + assert_equal 'custom message', model.errors.on(attribute) + end + + def assert_default_message_translation(model, attribute, type) + store_translations(:errors => { :messages => { type => 'default message' } }) + yield + model.valid? + assert_equal 'default message', model.errors.on(attribute) + end + def unique_topic - @unique ||= Topic.create :title => 'unique!' + @unique ||= Topic.create(:title => 'unique!') end def replied_topic @@ -30,886 +129,819 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase end end - def reset_callbacks(*models) - models.each do |model| - model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new) - model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new) - model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + # validates_confirmation_of + + test "#validates_confirmation_of given no custom message" do + expect_error_added(@topic, :title, :confirmation, :default => nil) do + Topic.validates_confirmation_of :title + @topic.title = 'title' + @topic.title_confirmation = 'foo' end end - def test_default_error_messages_is_deprecated - assert_deprecated('ActiveRecord::Errors.default_error_messages') do - ActiveRecord::Errors.default_error_messages + test "#validates_confirmation_of given a custom message" do + expect_error_added(@topic, :title, :confirmation, :default => 'custom') do + Topic.validates_confirmation_of :title, :message => 'custom' + @topic.title_confirmation = 'foo' end end - def test_percent_s_interpolation_syntax_in_error_messages_still_works - ActiveSupport::Deprecation.silence do - result = I18n.t :does_not_exist, :default => "%s interpolation syntax is deprecated", :value => 'this' - assert_equal result, "this interpolation syntax is deprecated" + test "#validates_confirmation_of finds the correct message translations" do + assert_message_translations(@topic, :title, :confirmation) do + Topic.validates_confirmation_of :title + @topic.title_confirmation = 'foo' end end - def test_percent_s_interpolation_syntax_in_error_messages_is_deprecated - assert_deprecated('using %s in messages') do - I18n.t :does_not_exist, :default => "%s interpolation syntax is deprected", :value => 'this' + # validates_acceptance_of + + test "#validates_acceptance_of given no custom message" do + expect_error_added(@topic, :title, :accepted, :default => nil) do + Topic.validates_acceptance_of :title, :allow_nil => false end end - def test_percent_d_interpolation_syntax_in_error_messages_still_works - ActiveSupport::Deprecation.silence do - result = I18n.t :does_not_exist, :default => "%d interpolation syntaxes are deprecated", :count => 2 - assert_equal result, "2 interpolation syntaxes are deprecated" + test "#validates_acceptance_of given a custom message" do + expect_error_added(@topic, :title, :accepted, :default => 'custom') do + Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false end end - def test_percent_d_interpolation_syntax_in_error_messages_is_deprecated - assert_deprecated('using %d in messages') do - I18n.t :does_not_exist, :default => "%d interpolation syntaxes are deprected", :count => 2 + test "#validates_acceptance_of finds the correct message translations" do + assert_message_translations(@topic, :title, :accepted) do + Topic.validates_acceptance_of :title, :allow_nil => false end end - # ActiveRecord::Errors - def test_errors_generate_message_translates_custom_model_attribute_key - - I18n.expects(:translate).with( - :topic, - { :count => 1, - :default => ['Topic'], - :scope => [:activerecord, :models] - } - ).returns('Topic') - - I18n.expects(:translate).with( - :"topic.title", - { :count => 1, - :default => ['Title'], - :scope => [:activerecord, :attributes] - } - ).returns('Title') + # validates_presence_of - I18n.expects(:translate).with( - :"models.topic.attributes.title.invalid", - :value => nil, - :scope => [:activerecord, :errors], - :default => [ - :"models.topic.invalid", - 'default from class def error 1', - :"messages.invalid"], - :attribute => "Title", - :model => "Topic" - ).returns('default from class def error 1') - - @topic.errors.generate_message :title, :invalid, :default => 'default from class def error 1' + test "#validates_presence_of given no custom message" do + expect_error_added(@topic, :title, :blank, :default => nil) do + Topic.validates_presence_of :title + end end - def test_errors_generate_message_translates_custom_model_attribute_keys_with_sti - - I18n.expects(:translate).with( - :reply, - { :count => 1, - :default => [:topic, 'Reply'], - :scope => [:activerecord, :models] - } - ).returns('Reply') - - I18n.expects(:translate).with( - :"reply.title", - { :count => 1, - :default => [:'topic.title', 'Title'], - :scope => [:activerecord, :attributes] - } - ).returns('Title') - - I18n.expects(:translate).with( - :"models.reply.attributes.title.invalid", - :value => nil, - :scope => [:activerecord, :errors], - :default => [ - :"models.reply.invalid", - :"models.topic.attributes.title.invalid", - :"models.topic.invalid", - 'default from class def', - :"messages.invalid"], - :model => 'Reply', - :attribute => 'Title' - ).returns("default from class def") - - Reply.new.errors.generate_message :title, :invalid, :default => 'default from class def' - + test "#validates_presence_of given a custom message" do + expect_error_added(@topic, :title, :blank, :default => 'custom') do + Topic.validates_presence_of :title, :message => 'custom' + end end - def test_errors_add_on_empty_generates_message - @topic.errors.expects(:generate_message).with(:title, :empty, {:default => nil}) - @topic.errors.add_on_empty :title + test "#validates_presence_of finds the correct message translations" do + assert_message_translations(@topic, :title, :blank) do + Topic.validates_presence_of :title + end end - def test_errors_add_on_empty_generates_message_with_custom_default_message - @topic.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'}) - @topic.errors.add_on_empty :title, 'custom' - end + # validates_length_of :too_short - def test_errors_add_on_blank_generates_message - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) - @topic.errors.add_on_blank :title + test "#validates_length_of (:too_short) and no custom message" do + expect_error_added(@topic, :title, :too_short, :default => nil, :count => 3) do + Topic.validates_length_of :title, :within => 3..5 + end end - def test_errors_add_on_blank_generates_message_with_custom_default_message - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) - @topic.errors.add_on_blank :title, 'custom' + test "#validates_length_of (:too_short) and a custom message" do + expect_error_added(@topic, :title, :too_short, :default => 'custom', :count => 3) do + Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' + end end - def test_errors_full_messages_translates_human_attribute_name_for_model_attributes - @topic.errors.instance_variable_set :@errors, { 'title' => ['empty'] } - I18n.expects(:translate).with(:"topic.title", :default => ['Title'], :scope => [:activerecord, :attributes], :count => 1).returns('Title') - @topic.errors.full_messages :locale => 'en' + test "#validates_length_of (:too_short) finds the correct message translations" do + assert_message_translations(@topic, :title, :too_short) do + Topic.validates_length_of :title, :within => 3..5 + end end - # ActiveRecord::Validations - # validates_confirmation_of w/ mocha - def test_validates_confirmation_of_generates_message - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil}) - @topic.valid? - end + # validates_length_of :too_long - def test_validates_confirmation_of_generates_message_with_custom_default_message - Topic.validates_confirmation_of :title, :message => 'custom' - @topic.title_confirmation = 'foo' - @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'}) - @topic.valid? + test "#validates_length_of (:too_long) and no custom message" do + expect_error_added(@topic, :title, :too_long, :default => nil, :count => 5) do + Topic.validates_length_of :title, :within => 3..5 + @topic.title = 'this title is too long' + end end - # validates_acceptance_of w/ mocha - - def test_validates_acceptance_of_generates_message - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => nil}) - @topic.valid? + test "#validates_length_of (:too_long) and a custom message" do + expect_error_added(@topic, :title, :too_long, :default => 'custom', :count => 5) do + Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' + @topic.title = 'this title is too long' + end end - def test_validates_acceptance_of_generates_message_with_custom_default_message - Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false - @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'}) - @topic.valid? + test "#validates_length_of (:too_long) finds the correct message translations" do + assert_message_translations(@topic, :title, :too_long) do + Topic.validates_length_of :title, :within => 3..5 + @topic.title = 'this title is too long' + end end - # validates_presence_of w/ mocha + # validates_length_of :is - def test_validates_presence_of_generates_message - Topic.validates_presence_of :title - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) - @topic.valid? + test "#validates_length_of (:is) and no custom message" do + expect_error_added(@topic, :title, :wrong_length, :default => nil, :count => 5) do + Topic.validates_length_of :title, :is => 5 + @topic.title = 'this title has the wrong length' + end end - def test_validates_presence_of_generates_message_with_custom_default_message - Topic.validates_presence_of :title, :message => 'custom' - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) - @topic.valid? + test "#validates_length_of (:is) and a custom message" do + expect_error_added(@topic, :title, :wrong_length, :default => 'custom', :count => 5) do + Topic.validates_length_of :title, :is => 5, :wrong_length => 'custom' + @topic.title = 'this title has the wrong length' + end end - def test_validates_length_of_within_generates_message_with_title_too_short - Topic.validates_length_of :title, :within => 3..5 - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil}) - @topic.valid? + test "#validates_length_of (:is) finds the correct message translations" do + assert_message_translations(@topic, :title, :wrong_length) do + Topic.validates_length_of :title, :is => 5 + @topic.title = 'this title has the wrong length' + end end - def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'}) - @topic.valid? - end + # validates_uniqueness_of - def test_validates_length_of_within_generates_message_with_title_too_long - Topic.validates_length_of :title, :within => 3..5 - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil}) - @topic.valid? + test "#validates_uniqueness_of and no custom message" do + expect_error_added(@topic, :title, :taken, :default => nil, :value => 'unique!') do + Topic.validates_uniqueness_of :title + @topic.title = unique_topic.title + end end - def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'}) - @topic.valid? + test "#validates_uniqueness_of and a custom message" do + expect_error_added(@topic, :title, :taken, :default => 'custom', :value => 'unique!') do + Topic.validates_uniqueness_of :title, :message => 'custom' + @topic.title = unique_topic.title + end end - # validates_length_of :within w/ mocha - - def test_validates_length_of_within_generates_message_with_title_too_short - Topic.validates_length_of :title, :within => 3..5 - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil}) - @topic.valid? + test "#validates_uniqueness_of finds the correct message translations" do + assert_message_translations(@topic, :title, :taken) do + Topic.validates_uniqueness_of :title + @topic.title = unique_topic.title + end end - def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'}) - @topic.valid? - end + # validates_format_of - def test_validates_length_of_within_generates_message_with_title_too_long - Topic.validates_length_of :title, :within => 3..5 - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil}) - @topic.valid? + test "#validates_format_of and no custom message" do + expect_error_added(@topic, :title, :invalid, :default => nil, :value => '72x') do + Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ + @topic.title = '72x' + end end - def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'}) - @topic.valid? - end - - # validates_length_of :is w/ mocha - - def test_validates_length_of_is_generates_message - Topic.validates_length_of :title, :is => 5 - @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil}) - @topic.valid? + test "#validates_format_of and a custom message" do + expect_error_added(@topic, :title, :invalid, :default => 'custom', :value => '72x') do + Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom' + @topic.title = '72x' + end end - def test_validates_length_of_is_generates_message_with_custom_default_message - Topic.validates_length_of :title, :is => 5, :message => 'custom' - @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'}) - @topic.valid? + test "#validates_format_of finds the correct message translations" do + assert_message_translations(@topic, :title, :invalid) do + Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ + @topic.title = '72x' + end end - # validates_uniqueness_of w/ mocha + # validates_inclusion_of - def test_validates_uniqueness_of_generates_message - Topic.validates_uniqueness_of :title - @topic.title = unique_topic.title - @topic.errors.expects(:generate_message).with(:title, :taken, {:default => nil, :value => 'unique!'}) - @topic.valid? + test "#validates_inclusion_of and no custom message" do + list = %w(a b c) + expect_error_added(@topic, :title, :inclusion, :default => nil, :value => 'z') do + Topic.validates_inclusion_of :title, :in => list + @topic.title = 'z' + end end - def test_validates_uniqueness_of_generates_message_with_custom_default_message - Topic.validates_uniqueness_of :title, :message => 'custom' - @topic.title = unique_topic.title - @topic.errors.expects(:generate_message).with(:title, :taken, {:default => 'custom', :value => 'unique!'}) - @topic.valid? + test "#validates_inclusion_of and a custom message" do + list = %w(a b c) + expect_error_added(@topic, :title, :inclusion, :default => 'custom', :value => 'z') do + Topic.validates_inclusion_of :title, :in => list, :message => 'custom' + @topic.title = 'z' + end end - # validates_format_of w/ mocha - - def test_validates_format_of_generates_message - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.title = '72x' - @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil}) - @topic.valid? - end - - def test_validates_format_of_generates_message_with_custom_default_message - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom' - @topic.title = '72x' - @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'}) - @topic.valid? + test "#validates_inclusion_of finds the correct message translations" do + list = %w(a b c) + assert_message_translations(@topic, :title, :inclusion) do + Topic.validates_inclusion_of :title, :in => list + @topic.title = 'z' + end end - # validates_inclusion_of w/ mocha + # validates_exclusion_of - def test_validates_inclusion_of_generates_message - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.title = 'z' - @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil}) - @topic.valid? + test "#validates_exclusion_of and no custom message" do + list = %w(a b c) + expect_error_added(@topic, :title, :exclusion, :default => nil, :value => 'a') do + Topic.validates_exclusion_of :title, :in => list + @topic.title = 'a' + end end - def test_validates_inclusion_of_generates_message_with_custom_default_message - Topic.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom' - @topic.title = 'z' - @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'}) - @topic.valid? + test "#validates_exclusion_of and a custom message" do + list = %w(a b c) + expect_error_added(@topic, :title, :exclusion, :default => 'custom', :value => 'a') do + Topic.validates_exclusion_of :title, :in => list, :message => 'custom' + @topic.title = 'a' + end end - # validates_exclusion_of w/ mocha - - def test_validates_exclusion_of_generates_message - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil}) - @topic.valid? + test "#validates_exclusion_of finds the correct message translations" do + list = %w(a b c) + assert_message_translations(@topic, :title, :exclusion) do + Topic.validates_exclusion_of :title, :in => list + @topic.title = 'a' + end end - def test_validates_exclusion_of_generates_message_with_custom_default_message - Topic.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'}) - @topic.valid? - end - - # validates_numericality_of without :only_integer w/ mocha - - def test_validates_numericality_of_generates_message - Topic.validates_numericality_of :title - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil}) - @topic.valid? - end + # validates_numericality_of :not_a_number, without :only_integer - def test_validates_numericality_of_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'}) - @topic.valid? + test "#validates_numericality_of (:not_a_number, w/o :only_integer) no custom message" do + expect_error_added(@topic, :title, :not_a_number, :default => nil, :value => 'a') do + Topic.validates_numericality_of :title + @topic.title = 'a' + end end - # validates_numericality_of with :only_integer w/ mocha - - def test_validates_numericality_of_only_integer_generates_message - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil}) - @topic.valid? + test "#validates_numericality_of (:not_a_number, w/o :only_integer) and a custom message" do + expect_error_added(@topic, :title, :not_a_number, :default => 'custom', :value => 'a') do + Topic.validates_numericality_of :title, :message => 'custom' + @topic.title = 'a' + end end - def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'}) - @topic.valid? + test "#validates_numericality_of (:not_a_number, w/o :only_integer) finds the correct message translations" do + assert_message_translations(@topic, :title, :not_a_number) do + Topic.validates_numericality_of :title + @topic.title = 'a' + end end - # validates_numericality_of :odd w/ mocha + # validates_numericality_of :not_a_number, with :only_integer - def test_validates_numericality_of_odd_generates_message - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil}) - @topic.valid? + test "#validates_numericality_of (:not_a_number, with :only_integer) no custom message" do + expect_error_added(@topic, :title, :not_a_number, :default => nil, :value => 'a') do + Topic.validates_numericality_of :title, :only_integer => true + @topic.title = 'a' + end end - def test_validates_numericality_of_odd_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom' - @topic.title = 0 - @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'}) - @topic.valid? + test "#validates_numericality_of (:not_a_number, with :only_integer) and a custom message" do + expect_error_added(@topic, :title, :not_a_number, :default => 'custom', :value => 'a') do + Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom' + @topic.title = 'a' + end end - # validates_numericality_of :less_than w/ mocha - - def test_validates_numericality_of_less_than_generates_message - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil}) - @topic.valid? + test "#validates_numericality_of (:not_a_number, with :only_integer) finds the correct message translations" do + assert_message_translations(@topic, :title, :not_a_number) do + Topic.validates_numericality_of :title, :only_integer => true + @topic.title = 'a' + end end - def test_validates_numericality_of_odd_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom' - @topic.title = 1 - @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'}) - @topic.valid? - end + # validates_numericality_of :odd - # validates_associated w/ mocha - - def test_validates_associated_generates_message - Topic.validates_associated :replies - replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil}) - replied_topic.valid? + test "#validates_numericality_of (:odd) no custom message" do + expect_error_added(@topic, :title, :odd, :default => nil, :value => 0) do + Topic.validates_numericality_of :title, :only_integer => true, :odd => true + @topic.title = 0 + end end - def test_validates_associated_generates_message_with_custom_default_message - Topic.validates_associated :replies - replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil}) - replied_topic.valid? + test "#validates_numericality_of (:odd) and a custom message" do + expect_error_added(@topic, :title, :odd, :default => 'custom', :value => 0) do + Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom' + @topic.title = 0 + end end - # validates_confirmation_of w/o mocha - - def test_validates_confirmation_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:confirmation => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}} - - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#validates_numericality_of (:odd) finds the correct message translations" do + assert_message_translations(@topic, :title, :odd) do + Topic.validates_numericality_of :title, :only_integer => true, :odd => true + @topic.title = 0 + end end - def test_validates_confirmation_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}} + # validates_numericality_of :even - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#validates_numericality_of (:even) no custom message" do + expect_error_added(@topic, :title, :even, :default => nil, :value => 1) do + Topic.validates_numericality_of :title, :only_integer => true, :even => true + @topic.title = 1 + end end - # validates_acceptance_of w/o mocha - - def test_validates_acceptance_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:accepted => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}} - - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) - end - - def test_validates_acceptance_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}} - - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) - end - - # validates_presence_of w/o mocha - - def test_validates_presence_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:blank => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}} - - Topic.validates_presence_of :title - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) - end - - def test_validates_presence_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}} - - Topic.validates_presence_of :title - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) - end - - # validates_length_of :within w/o mocha - - def test_validates_length_of_within_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:too_short => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}} - - Topic.validates_length_of :title, :within => 3..5 - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#validates_numericality_of (:even) and a custom message" do + expect_error_added(@topic, :title, :even, :default => 'custom', :value => 1) do + Topic.validates_numericality_of :title, :only_integer => true, :even => true, :message => 'custom' + @topic.title = 1 + end end - def test_validates_length_of_within_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}} - - Topic.validates_length_of :title, :within => 3..5 - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#validates_numericality_of (:even) finds the correct message translations" do + assert_message_translations(@topic, :title, :even) do + Topic.validates_numericality_of :title, :only_integer => true, :even => true + @topic.title = 1 + end end - # validates_length_of :is w/o mocha - - def test_validates_length_of_is_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} + # validates_numericality_of :less_than - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#validates_numericality_of (:less_than) no custom message" do + expect_error_added(@topic, :title, :less_than, :default => nil, :value => 1, :count => 0) do + Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 + @topic.title = 1 + end end - def test_validates_length_of_is_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} - - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#validates_numericality_of (:less_than) and a custom message" do + expect_error_added(@topic, :title, :less_than, :default => 'custom', :value => 1, :count => 0) do + Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom' + @topic.title = 1 + end end - # validates_uniqueness_of w/o mocha - - def test_validates_length_of_is_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} - - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#validates_numericality_of (:less_than) finds the correct message translations" do + assert_message_translations(@topic, :title, :less_than) do + Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 + @topic.title = 1 + end end - def test_validates_length_of_is_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} + # validates_associated - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#validates_associated no custom message" do + expect_error_added(replied_topic, :replies, :invalid, :default => nil, :value => replied_topic.replies) do + Topic.validates_associated :replies + end end - - # validates_format_of w/o mocha - - def test_validates_format_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:invalid => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} - - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#validates_associated and a custom message" do + expect_error_added(replied_topic, :replies, :invalid, :default => 'custom', :value => replied_topic.replies) do + Topic.validates_associated :replies, :message => 'custom' + end end - def test_validates_format_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} - - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#validates_associated finds the correct message translations" do + assert_message_translations(replied_topic, :replies, :invalid) do + Topic.validates_associated :replies + end end - - # validates_inclusion_of w/o mocha - - def test_validates_inclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:inclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}} - - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) - end - - def test_validates_inclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}} - - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) - end - - # validates_exclusion_of w/o mocha - - def test_validates_exclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:exclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}} - - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) - end - - def test_validates_exclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}} - - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) - end - - # validates_numericality_of without :only_integer w/o mocha - - def test_validates_numericality_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title - @topic.title = 'a' - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) - end - - def test_validates_numericality_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) - end - - # validates_numericality_of with :only_integer w/o mocha - - def test_validates_numericality_of_only_integer_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) - end - - def test_validates_numericality_of_only_integer_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) - end - - # validates_numericality_of :odd w/o mocha - - def test_validates_numericality_of_odd_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:odd => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) - end - - def test_validates_numericality_of_odd_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) - end - - # validates_numericality_of :less_than w/o mocha - - def test_validates_numericality_of_less_than_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:less_than => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) - end - - def test_validates_numericality_of_less_than_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) - end - - - # validates_associated w/o mocha - - def test_validates_associated_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} - - Topic.validates_associated :replies - replied_topic.valid? - assert_equal 'custom message', replied_topic.errors.on(:replies) - end - - def test_validates_associated_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} - - Topic.validates_associated :replies - replied_topic.valid? - assert_equal 'global message', replied_topic.errors.on(:replies) - end - - def test_validations_with_message_symbol_must_translate - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:custom_error => "I am a custom error"}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal "I am a custom error", @topic.errors.on(:title) - end - - def test_validates_with_message_symbol_must_translate_per_attribute - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal "I am a custom error", @topic.errors.on(:title) - end - - def test_validates_with_message_symbol_must_translate_per_model - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:custom_error => "I am a custom error"}}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal "I am a custom error", @topic.errors.on(:title) - end - - def test_validates_with_message_string - Topic.validates_presence_of :title, :message => "I am a custom error" - @topic.title = nil - @topic.valid? - assert_equal "I am a custom error", @topic.errors.on(:title) - end - end -class ActiveRecordValidationsGenerateMessageI18nTests < Test::Unit::TestCase + +# ACTIVERECORD ERROR +# +# * test that it passes given interpolation arguments, the human model name and human attribute name +# * test that it looks messages up with the the correct keys +# * test that it looks up the correct default messages + +class ActiveRecordErrorI18nTests < ActiveSupport::TestCase + include ActiveRecordValidationsI18nTestHelper + def setup - reset_callbacks Topic - @topic = Topic.new - I18n.backend.store_translations :'en', { - :activerecord => { - :errors => { - :messages => { - :inclusion => "is not included in the list", - :exclusion => "is reserved", - :invalid => "is invalid", - :confirmation => "doesn't match confirmation", - :accepted => "must be accepted", - :empty => "can't be empty", - :blank => "can't be blank", - :too_long => "is too long (maximum is {{count}} characters)", - :too_short => "is too short (minimum is {{count}} characters)", - :wrong_length => "is the wrong length (should be {{count}} characters)", - :taken => "has already been taken", - :not_a_number => "is not a number", - :greater_than => "must be greater than {{count}}", - :greater_than_or_equal_to => "must be greater than or equal to {{count}}", - :equal_to => "must be equal to {{count}}", - :less_than => "must be less than {{count}}", - :less_than_or_equal_to => "must be less than or equal to {{count}}", - :odd => "must be odd", - :even => "must be even" + @reply = Reply.new + @old_backend, I18n.backend = I18n.backend, I18n::Backend::Simple.new + end + + def teardown + I18n.backend = @old_backend + I18n.locale = nil + end + + def assert_error_message(message, *args) + assert_equal message, ActiveRecord::Error.new(@reply, *args).message + end + + def assert_full_message(message, *args) + assert_equal message, ActiveRecord::Error.new(@reply, *args).full_message + end + + test "#generate_message passes the model attribute value for interpolation" do + store_translations(:errors => { :messages => { :foo => "You fooed: {{value}}." } }) + @reply.title = "da title" + assert_error_message 'You fooed: da title.', :title, :foo + end + + test "#generate_message passes the human_name of the model for interpolation" do + store_translations( + :errors => { :messages => { :foo => "You fooed: {{model}}." } }, + :models => { :topic => 'da topic' } + ) + assert_error_message 'You fooed: da topic.', :title, :foo + end + + test "#generate_message passes the human_name of the attribute for interpolation" do + store_translations( + :errors => { :messages => { :foo => "You fooed: {{attribute}}." } }, + :attributes => { :topic => { :title => 'da topic title' } } + ) + assert_error_message 'You fooed: da topic title.', :title, :foo + end + + # generate_message will look up the key for the error message (e.g. :blank) in these namespaces: + # + # activerecord.errors.models.reply.attributes.title + # activerecord.errors.models.reply + # activerecord.errors.models.topic.attributes.title + # activerecord.errors.models.topic + # [default from class level :validates_foo statement if this is a String] + # activerecord.errors.messages + + test "#generate_message key fallbacks (given a String as key)" do + store_translations( + :errors => { + :models => { + :reply => { + :attributes => { :title => { :custom => 'activerecord.errors.models.reply.attributes.title.custom' } }, + :custom => 'activerecord.errors.models.reply.custom' + }, + :topic => { + :attributes => { :title => { :custom => 'activerecord.errors.models.topic.attributes.title.custom' } }, + :custom => 'activerecord.errors.models.topic.custom' } + }, + :messages => { + :custom => 'activerecord.errors.messages.custom', + :kaputt => 'activerecord.errors.messages.kaputt' } } - } + ) + + assert_error_message 'activerecord.errors.models.reply.attributes.title.custom', :title, :kaputt, :message => 'custom' + delete_translation :'activerecord.errors.models.reply.attributes.title.custom' + + assert_error_message 'activerecord.errors.models.reply.custom', :title, :kaputt, :message => 'custom' + delete_translation :'activerecord.errors.models.reply.custom' + + assert_error_message 'activerecord.errors.models.topic.attributes.title.custom', :title, :kaputt, :message => 'custom' + delete_translation :'activerecord.errors.models.topic.attributes.title.custom' + + assert_error_message 'activerecord.errors.models.topic.custom', :title, :kaputt, :message => 'custom' + delete_translation :'activerecord.errors.models.topic.custom' + + assert_error_message 'activerecord.errors.messages.custom', :title, :kaputt, :message => 'custom' + delete_translation :'activerecord.errors.messages.custom' + + # Implementing this would clash with the AR default behaviour of using validates_foo :message => 'foo' + # as an untranslated string. I.e. at this point we can either fall back to the given string from the + # class-level macro (validates_*) or fall back to the default message for this validation type. + # assert_error_message 'activerecord.errors.messages.kaputt', :title, :kaputt, :message => 'custom' + + assert_error_message 'custom', :title, :kaputt, :message => 'custom' end - def reset_callbacks(*models) - models.each do |model| - model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new) - model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new) - model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + test "#generate_message key fallbacks (given a Symbol as key)" do + store_translations( + :errors => { + :models => { + :reply => { + :attributes => { :title => { :kaputt => 'activerecord.errors.models.reply.attributes.title.kaputt' } }, + :kaputt => 'activerecord.errors.models.reply.kaputt' + }, + :topic => { + :attributes => { :title => { :kaputt => 'activerecord.errors.models.topic.attributes.title.kaputt' } }, + :kaputt => 'activerecord.errors.models.topic.kaputt' + } + }, + :messages => { + :kaputt => 'activerecord.errors.messages.kaputt' + } + } + ) + + assert_error_message 'activerecord.errors.models.reply.attributes.title.kaputt', :title, :kaputt + delete_translation :'activerecord.errors.models.reply.attributes.title.kaputt' + + assert_error_message 'activerecord.errors.models.reply.kaputt', :title, :kaputt + delete_translation :'activerecord.errors.models.reply.kaputt' + + assert_error_message 'activerecord.errors.models.topic.attributes.title.kaputt', :title, :kaputt + delete_translation :'activerecord.errors.models.topic.attributes.title.kaputt' + + assert_error_message 'activerecord.errors.models.topic.kaputt', :title, :kaputt + delete_translation :'activerecord.errors.models.topic.kaputt' + + assert_error_message 'activerecord.errors.messages.kaputt', :title, :kaputt + end + + # full_messages + + test "#full_message with no format present" do + store_translations(:errors => { :messages => { :kaputt => 'is kaputt' } }) + assert_full_message 'Title is kaputt', :title, :kaputt + end + + test "#full_message with a format present" do + store_translations(:errors => { :messages => { :kaputt => 'is kaputt' }, :full_messages => { :format => '{{attribute}}: {{message}}' } }) + assert_full_message 'Title: is kaputt', :title, :kaputt + end + + test "#full_message with a type specific format present" do + store_translations(:errors => { :messages => { :kaputt => 'is kaputt' }, :full_messages => { :kaputt => '{{attribute}} {{message}}!' } }) + assert_full_message 'Title is kaputt!', :title, :kaputt + end + + test "#full_message with class-level specified custom message" do + store_translations(:errors => { :messages => { :broken => 'is kaputt' }, :full_messages => { :broken => '{{attribute}} {{message}}?!' } }) + assert_full_message 'Title is kaputt?!', :title, :kaputt, :message => :broken + end + + # switch locales + + test "#message allows to switch locales" do + store_translations(:en, :errors => { :messages => { :kaputt => 'is kaputt' } }) + store_translations(:de, :errors => { :messages => { :kaputt => 'ist kaputt' } }) + + assert_error_message 'is kaputt', :title, :kaputt + I18n.locale = :de + assert_error_message 'ist kaputt', :title, :kaputt + I18n.locale = :en + assert_error_message 'is kaputt', :title, :kaputt + end + + test "#full_message allows to switch locales" do + store_translations(:en, :errors => { :messages => { :kaputt => 'is kaputt' } }, :attributes => { :topic => { :title => 'The title' } }) + store_translations(:de, :errors => { :messages => { :kaputt => 'ist kaputt' } }, :attributes => { :topic => { :title => 'Der Titel' } }) + + assert_full_message 'The title is kaputt', :title, :kaputt + I18n.locale = :de + assert_full_message 'Der Titel ist kaputt', :title, :kaputt + I18n.locale = :en + assert_full_message 'The title is kaputt', :title, :kaputt + end +end + +# ACTIVERECORD DEFAULT ERROR MESSAGES +# +# * test that Error generates the default error messages + +class ActiveRecordDefaultErrorMessagesI18nTests < ActiveSupport::TestCase + def assert_default_error_message(message, *args) + assert_equal message, error_message(*args) + end + + def error_message(*args) + ActiveRecord::Error.new(Topic.new, :title, *args).message + end + + # used by: validates_inclusion_of + test "default error message: inclusion" do + assert_default_error_message 'is not included in the list', :inclusion, :value => 'title' + end + + # used by: validates_exclusion_of + test "default error message: exclusion" do + assert_default_error_message 'is reserved', :exclusion, :value => 'title' + end + + # used by: validates_associated and validates_format_of + test "default error message: invalid" do + assert_default_error_message 'is invalid', :invalid, :value => 'title' + end + + # used by: validates_confirmation_of + test "default error message: confirmation" do + assert_default_error_message "doesn't match confirmation", :confirmation, :default => nil + end + + # used by: validates_acceptance_of + test "default error message: accepted" do + assert_default_error_message "must be accepted", :accepted + end + + # used by: add_on_empty + test "default error message: empty" do + assert_default_error_message "can't be empty", :empty + end + + # used by: add_on_blank + test "default error message: blank" do + assert_default_error_message "can't be blank", :blank + end + + # used by: validates_length_of + test "default error message: too_long" do + assert_default_error_message "is too long (maximum is 10 characters)", :too_long, :count => 10 + end + + # used by: validates_length_of + test "default error message: too_short" do + assert_default_error_message "is too short (minimum is 10 characters)", :too_short, :count => 10 + end + + # used by: validates_length_of + test "default error message: wrong_length" do + assert_default_error_message "is the wrong length (should be 10 characters)", :wrong_length, :count => 10 + end + + # used by: validates_uniqueness_of + test "default error message: taken" do + assert_default_error_message "has already been taken", :taken, :value => 'title' + end + + # used by: validates_numericality_of + test "default error message: not_a_number" do + assert_default_error_message "is not a number", :not_a_number, :value => 'title' + end + + # used by: validates_numericality_of + test "default error message: greater_than" do + assert_default_error_message "must be greater than 10", :greater_than, :value => 'title', :count => 10 + end + + # used by: validates_numericality_of + test "default error message: greater_than_or_equal_to" do + assert_default_error_message "must be greater than or equal to 10", :greater_than_or_equal_to, :value => 'title', :count => 10 + end + + # used by: validates_numericality_of + test "default error message: equal_to" do + assert_default_error_message "must be equal to 10", :equal_to, :value => 'title', :count => 10 + end + + # used by: validates_numericality_of + test "default error message: less_than" do + assert_default_error_message "must be less than 10", :less_than, :value => 'title', :count => 10 + end + + # used by: validates_numericality_of + test "default error message: less_than_or_equal_to" do + assert_default_error_message "must be less than or equal to 10", :less_than_or_equal_to, :value => 'title', :count => 10 + end + + # used by: validates_numericality_of + test "default error message: odd" do + assert_default_error_message "must be odd", :odd, :value => 'title', :count => 10 + end + + # used by: validates_numericality_of + test "default error message: even" do + assert_default_error_message "must be even", :even, :value => 'title', :count => 10 + end + + test "custom message string interpolation" do + assert_equal 'custom message title', error_message(:invalid, :default => 'custom message {{value}}', :value => 'title') + end +end + +# ACTIVERECORD VALIDATION ERROR MESSAGES - FULL STACK +# +# * test a few combinations full stack to ensure the tests above are correct + +class I18nPerson < Person +end + +class ActiveRecordValidationsI18nFullStackTests < ActiveSupport::TestCase + include ActiveRecordValidationsI18nTestHelper + + def setup + reset_callbacks(I18nPerson) + @old_backend, I18n.backend = I18n.backend, I18n::Backend::Simple.new + @person = I18nPerson.new + end + + def teardown + reset_callbacks(I18nPerson) + I18n.backend = @old_backend + end + + def assert_name_invalid(message) + yield + @person.valid? + assert_equal message, @person.errors.on(:name) + end + + # Symbols as class-level validation messages + + test "Symbol as class level validation message translated per attribute (translation on child class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:i18n_person => {:attributes => {:name => {:broken => "is broken"}}}}} + I18nPerson.validates_presence_of :name, :message => :broken end end - # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value) - def test_generate_message_inclusion_with_default_message - assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title') + test "Symbol as class level validation message translated per attribute (translation on base class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:person => {:attributes => {:name => {:broken => "is broken"}}}}} + I18nPerson.validates_presence_of :name, :message => :broken + end end - def test_generate_message_inclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title') + test "Symbol as class level validation message translated per model (translation on child class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:i18n_person => {:broken => "is broken"}}} + I18nPerson.validates_presence_of :name, :message => :broken + end end - # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value) - def test_generate_message_exclusion_with_default_message - assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title') + test "Symbol as class level validation message translated per model (translation on base class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:person => {:broken => "is broken"}}} + I18nPerson.validates_presence_of :name, :message => :broken + end end - def test_generate_message_exclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title') + test "Symbol as class level validation message translated as error message" do + assert_name_invalid("is broken") do + store_translations :errors => {:messages => {:broken => "is broken"}} + I18nPerson.validates_presence_of :name, :message => :broken + end end - # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) - # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) - def test_generate_message_invalid_with_default_message - assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title') + # Strings as class-level validation messages + + test "String as class level validation message translated per attribute (translation on child class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:i18n_person => {:attributes => {:name => {"is broken" => "is broken"}}}}} + I18nPerson.validates_presence_of :name, :message => "is broken" + end end - def test_generate_message_invalid_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title') + test "String as class level validation message translated per attribute (translation on base class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:person => {:attributes => {:name => {"is broken" => "is broken"}}}}} + I18nPerson.validates_presence_of :name, :message => "is broken" + end end - # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message]) - def test_generate_message_confirmation_with_default_message - assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil) + test "String as class level validation message translated per model (translation on child class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:i18n_person => {"is broken" => "is broken"}}} + I18nPerson.validates_presence_of :name, :message => "is broken" + end end - def test_generate_message_confirmation_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message') + test "String as class level validation message translated per model (translation on base class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:person => {"is broken" => "is broken"}}} + I18nPerson.validates_presence_of :name, :message => "is broken" + end end - # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message]) - def test_generate_message_accepted_with_default_message - assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil) + test "String as class level validation message translated as error message" do + assert_name_invalid("is broken") do + store_translations :errors => {:messages => {"is broken" => "is broken"}} + I18nPerson.validates_presence_of :name, :message => "is broken" + end end - def test_generate_message_accepted_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message') + test "String as class level validation message not translated (uses message as default)" do + assert_name_invalid("is broken!") do + I18nPerson.validates_presence_of :name, :message => "is broken!" + end + end +end + +class ActiveRecordValidationsI18nFullMessagesFullStackTests < ActiveSupport::TestCase + include ActiveRecordValidationsI18nTestHelper + + def setup + reset_callbacks(I18nPerson) + @old_backend, I18n.backend = I18n.backend, I18n::Backend::Simple.new + @person = I18nPerson.new + end + + def teardown + reset_callbacks(I18nPerson) + I18n.backend = @old_backend + end + + def assert_full_message(message) + yield + @person.valid? + assert_equal message, @person.errors.full_messages.join + end + + test "full_message format stored per custom error message key" do + assert_full_message("Name is broken!") do + store_translations :errors => { :messages => { :broken => 'is broken' }, :full_messages => { :broken => '{{attribute}} {{message}}!' } } + I18nPerson.validates_presence_of :name, :message => :broken + end + end + + test "full_message format stored per error type" do + assert_full_message("Name can't be blank!") do + store_translations :errors => { :full_messages => { :blank => '{{attribute}} {{message}}!' } } + I18nPerson.validates_presence_of :name + end + end + # ActiveRecord#RecordInvalid exception + + test "full_message format stored as default" do + assert_full_message("Name: can't be blank") do + store_translations :errors => { :full_messages => { :format => '{{attribute}}: {{message}}' } } + I18nPerson.validates_presence_of :name + end + end + test "RecordInvalid exception can be localized" do + topic = Topic.new + topic.errors.add(:title, :invalid) + topic.errors.add(:title, :blank) + assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message end - - # add_on_empty: generate_message(attr, :empty, :default => custom_message) - def test_generate_message_empty_with_default_message - assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil) - end - - def test_generate_message_empty_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message') - end - - # add_on_blank: generate_message(attr, :blank, :default => custom_message) - def test_generate_message_blank_with_default_message - assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil) - end - - def test_generate_message_blank_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message') - end - - # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end) - def test_generate_message_too_long_with_default_message - assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10) - end - - def test_generate_message_too_long_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10) - end - - # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin) - def test_generate_message_too_short_with_default_message - assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10) - end - - def test_generate_message_too_short_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10) - end - - # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value) - def test_generate_message_wrong_length_with_default_message - assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10) - end - - def test_generate_message_wrong_length_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10) - end - - # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message]) - def test_generate_message_taken_with_default_message - assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title') - end - - def test_generate_message_taken_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message]) - def test_generate_message_not_a_number_with_default_message - assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title') - end - - def test_generate_message_not_a_number_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message]) - def test_generate_message_greater_than_with_default_message - assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_greater_than_or_equal_to_with_default_message - assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_equal_to_with_default_message - assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_less_than_with_default_message - assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_less_than_or_equal_to_with_default_message - assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_odd_with_default_message - assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_even_with_default_message - assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10) - end - end diff --git a/vendor/rails/activerecord/test/cases/validations_test.rb b/vendor/rails/activerecord/test/cases/validations_test.rb index c20f5ae6..4c023c37 100644 --- a/vendor/rails/activerecord/test/cases/validations_test.rb +++ b/vendor/rails/activerecord/test/cases/validations_test.rb @@ -345,13 +345,13 @@ class ValidationsTest < ActiveRecord::TestCase def test_validate_uniqueness Topic.validates_uniqueness_of(:title) - t = Topic.new("title" => "I'm unique!") + t = Topic.new("title" => "I'm uniqué!") assert t.save, "Should save t as unique" t.content = "Remaining unique" assert t.save, "Should still save t as unique" - t2 = Topic.new("title" => "I'm unique!") + t2 = Topic.new("title" => "I'm uniqué!") assert !t2.valid?, "Shouldn't be valid" assert !t2.save, "Shouldn't save t2 as unique" assert_equal "has already been taken", t2.errors.on(:title) @@ -539,6 +539,16 @@ class ValidationsTest < ActiveRecord::TestCase assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" end + def test_validate_uniqueness_with_limit_and_utf8 + with_kcode('UTF8') do + # Event.title is limited to 5 characters + e1 = Event.create(:title => "一二三四五") + assert e1.valid?, "Could not create an event with a unique, 5 character title" + e2 = Event.create(:title => "一二三四五六七八") + assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" + end + end + def test_validate_straight_inheritance_uniqueness w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork") assert w1.valid?, "Saving w1" diff --git a/vendor/rails/activerecord/test/cases/xml_serialization_test.rb b/vendor/rails/activerecord/test/cases/xml_serialization_test.rb index b4999766..d8d952be 100644 --- a/vendor/rails/activerecord/test/cases/xml_serialization_test.rb +++ b/vendor/rails/activerecord/test/cases/xml_serialization_test.rb @@ -4,6 +4,7 @@ require 'models/post' require 'models/author' require 'models/tagging' require 'models/comment' +require 'models/company_in_module' class XmlSerializationTest < ActiveRecord::TestCase def test_should_serialize_default_root @@ -129,6 +130,25 @@ class NilXmlSerializationTest < ActiveRecord::TestCase end end +class DatabaseConnectedXmlModuleSerializationTest < ActiveRecord::TestCase + + fixtures :projects, :developers, :developers_projects + + def test_module + project = MyApplication::Business::Project.find :first + xml = project.to_xml + assert_match %r{}, xml + assert_match %r{}, xml + end + + def test_module_with_include + project = MyApplication::Business::Project.find :first + xml = project.to_xml :include => :developers + assert_match %r{}, xml + assert_match %r{}, xml + end +end + class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase fixtures :authors, :posts # to_xml used to mess with the hash the user provided which diff --git a/vendor/rails/activerecord/test/fixtures/posts.yml b/vendor/rails/activerecord/test/fixtures/posts.yml index 92e5d190..f8174931 100644 --- a/vendor/rails/activerecord/test/fixtures/posts.yml +++ b/vendor/rails/activerecord/test/fixtures/posts.yml @@ -4,6 +4,7 @@ welcome: title: Welcome to the weblog body: Such a lovely day comments_count: 2 + taggings_count: 1 type: Post thinking: @@ -11,6 +12,8 @@ thinking: author_id: 1 title: So I was thinking body: Like I hopefully always am + comments_count: 1 + taggings_count: 1 type: SpecialPost authorless: diff --git a/vendor/rails/activerecord/test/models/author.rb b/vendor/rails/activerecord/test/models/author.rb index b844c7cc..f264f980 100644 --- a/vendor/rails/activerecord/test/models/author.rb +++ b/vendor/rails/activerecord/test/models/author.rb @@ -1,5 +1,6 @@ class Author < ActiveRecord::Base has_many :posts + has_many :very_special_comments, :through => :posts has_many :posts_with_comments, :include => :comments, :class_name => "Post" has_many :popular_grouped_posts, :include => :comments, :class_name => "Post", :group => "type", :having => "SUM(comments_count) > 1", :select => "type" has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id' diff --git a/vendor/rails/activerecord/test/models/comment.rb b/vendor/rails/activerecord/test/models/comment.rb index f7f07c10..399dea9f 100644 --- a/vendor/rails/activerecord/test/models/comment.rb +++ b/vendor/rails/activerecord/test/models/comment.rb @@ -1,6 +1,10 @@ class Comment < ActiveRecord::Base named_scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'" - + named_scope :for_first_post, :conditions => { :post_id => 1 } + named_scope :for_first_author, + :joins => :post, + :conditions => { "posts.author_id" => 1 } + belongs_to :post, :counter_cache => true def self.what_are_you diff --git a/vendor/rails/activerecord/test/models/company.rb b/vendor/rails/activerecord/test/models/company.rb index 2a65b034..d4bbbebc 100644 --- a/vendor/rails/activerecord/test/models/company.rb +++ b/vendor/rails/activerecord/test/models/company.rb @@ -9,6 +9,8 @@ class Company < AbstractCompany validates_presence_of :name has_one :dummy_account, :foreign_key => "firm_id", :class_name => "Account" + has_many :contracts + has_many :developers, :through => :contracts def arbitrary_method "I am Jack's profound disappointment" diff --git a/vendor/rails/activerecord/test/models/company_in_module.rb b/vendor/rails/activerecord/test/models/company_in_module.rb index 7f02403d..e0715259 100644 --- a/vendor/rails/activerecord/test/models/company_in_module.rb +++ b/vendor/rails/activerecord/test/models/company_in_module.rb @@ -11,7 +11,7 @@ module MyApplication has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id" has_many :clients_using_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}' - has_one :account, :dependent => :destroy + has_one :account, :class_name => 'MyApplication::Billing::Account', :dependent => :destroy end class Client < Company diff --git a/vendor/rails/activerecord/test/models/contract.rb b/vendor/rails/activerecord/test/models/contract.rb new file mode 100644 index 00000000..5235bfa3 --- /dev/null +++ b/vendor/rails/activerecord/test/models/contract.rb @@ -0,0 +1,5 @@ +class Contract < ActiveRecord::Base + belongs_to :company + belongs_to :developer +end + diff --git a/vendor/rails/activerecord/test/models/organization.rb b/vendor/rails/activerecord/test/models/organization.rb index d79d5037..c8572616 100644 --- a/vendor/rails/activerecord/test/models/organization.rb +++ b/vendor/rails/activerecord/test/models/organization.rb @@ -1,4 +1,6 @@ class Organization < ActiveRecord::Base has_many :member_details has_many :members, :through => :member_details + + named_scope :clubs, { :from => 'clubs' } 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 201d96dc..9594dc30 100644 --- a/vendor/rails/activerecord/test/models/topic.rb +++ b/vendor/rails/activerecord/test/models/topic.rb @@ -35,8 +35,6 @@ 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" has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title" diff --git a/vendor/rails/activerecord/test/schema/postgresql_specific_schema.rb b/vendor/rails/activerecord/test/schema/postgresql_specific_schema.rb index 576a4d03..3d8911bf 100644 --- a/vendor/rails/activerecord/test/schema/postgresql_specific_schema.rb +++ b/vendor/rails/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,7 +1,7 @@ ActiveRecord::Schema.define do %w(postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings - postgresql_oids defaults geometrics).each do |table_name| + postgresql_oids postgresql_xml_data_type defaults geometrics).each do |table_name| execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" end @@ -100,4 +100,15 @@ _SQL obj_id OID ); _SQL -end \ No newline at end of file + + begin + execute <<_SQL + CREATE TABLE postgresql_xml_data_type ( + id SERIAL PRIMARY KEY, + data xml + ); +_SQL +rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table + end +end + diff --git a/vendor/rails/activerecord/test/schema/schema.rb b/vendor/rails/activerecord/test/schema/schema.rb index d080140e..87fad0eb 100644 --- a/vendor/rails/activerecord/test/schema/schema.rb +++ b/vendor/rails/activerecord/test/schema/schema.rb @@ -125,6 +125,10 @@ ActiveRecord::Schema.define do t.integer :extendedWarranty, :null => false end + create_table :contracts, :force => true do |t| + t.integer :developer_id + t.integer :company_id + end create_table :customers, :force => true do |t| t.string :name diff --git a/vendor/rails/activeresource/CHANGELOG b/vendor/rails/activeresource/CHANGELOG index 11424163..dde2d76d 100644 --- a/vendor/rails/activeresource/CHANGELOG +++ b/vendor/rails/activeresource/CHANGELOG @@ -1,7 +1,19 @@ +*2.3.4 (September 4, 2009)* + +* Add support for errors in JSON format. #1956 [Fabien Jakimowicz] + +* Recognizes 410 as Resource Gone. #2316 [Jordan Brough, Jatinder Singh] + +* More thorough SSL support. #2370 [Roy Nicholson] + +* HTTP proxy support. #2133 [Marshall Huss, Sébastien Dabet] + + *2.3.3 (July 12, 2009)* * No changes, just a version bump. + *2.3.2 [Final] (March 15, 2009)* * Nothing new, just included in 2.3.2 diff --git a/vendor/rails/activeresource/Rakefile b/vendor/rails/activeresource/Rakefile index c6591ccd..a8decd94 100644 --- a/vendor/rails/activeresource/Rakefile +++ b/vendor/rails/activeresource/Rakefile @@ -66,7 +66,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.3' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.4' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'active_resource' diff --git a/vendor/rails/activeresource/lib/active_resource/base.rb b/vendor/rails/activeresource/lib/active_resource/base.rb index 4a4ee9f4..e25002b6 100644 --- a/vendor/rails/activeresource/lib/active_resource/base.rb +++ b/vendor/rails/activeresource/lib/active_resource/base.rb @@ -93,6 +93,8 @@ module ActiveResource # # Many REST APIs will require authentication, usually in the form of basic # HTTP authentication. Authentication can be specified by: + # + # === HTTP Basic Authentication # * putting the credentials in the URL for the +site+ variable. # # class Person < ActiveResource::Base @@ -112,6 +114,19 @@ module ActiveResource # # Note: Some values cannot be provided in the URL passed to site. e.g. email addresses # as usernames. In those situations you should use the separate user and password option. + # + # === Certificate Authentication + # + # * End point uses an X509 certificate for authentication. See ssl_options= for all options. + # + # class Person < ActiveResource::Base + # self.site = "https://secure.api.people.com/" + # self.ssl_options = {:cert => OpenSSL::X509::Certificate.new(File.open(pem_file)) + # :key => OpenSSL::PKey::RSA.new(File.open(pem_file)), + # :ca_path => "/path/to/OpenSSL/formatted/CA_Certs", + # :verify_mode => OpenSSL::SSL::VERIFY_PEER} + # end + # # == Errors & Validation # # Error handling and validation is handled in much the same manner as you're used to seeing in @@ -138,6 +153,7 @@ module ActiveResource # * 404 - ActiveResource::ResourceNotFound # * 405 - ActiveResource::MethodNotAllowed # * 409 - ActiveResource::ResourceConflict + # * 410 - ActiveResource::ResourceGone # * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors) # * 401..499 - ActiveResource::ClientError # * 500..599 - ActiveResource::ServerError @@ -158,7 +174,7 @@ module ActiveResource # # Active Resource supports validations on resources and will return errors if any these validations fail # (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by - # a response code of 422 and an XML representation of the validation errors. The save operation will + # a response code of 422 and an XML or JSON representation of the validation errors. The save operation will # then fail (with a false return value) and the validation errors can be accessed on the resource in question. # # ryan = Person.find(1) @@ -167,10 +183,14 @@ module ActiveResource # # # When # # PUT http://api.people.com:3000/people/1.xml + # # or + # # PUT http://api.people.com:3000/people/1.json # # is requested with invalid values, the response is: # # # # Response (422): # # First cannot be empty + # # or + # # {"errors":["First cannot be empty"]} # # # # ryan.errors.invalid?(:first) # => true @@ -246,6 +266,22 @@ module ActiveResource end end + # Gets the \proxy variable if a proxy is required + def proxy + # Not using superclass_delegating_reader. See +site+ for explanation + if defined?(@proxy) + @proxy + elsif superclass != Object && superclass.proxy + superclass.proxy.dup.freeze + end + end + + # Sets the URI of the http proxy to the value in the +proxy+ argument. + def proxy=(proxy) + @connection = nil + @proxy = proxy.nil? ? nil : create_proxy_uri_from(proxy) + end + # Gets the \user for REST HTTP authentication. def user # Not using superclass_delegating_reader. See +site+ for explanation @@ -315,15 +351,42 @@ module ActiveResource end end + # Options that will get applied to an SSL connection. + # + # * :key - An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. + # * :cert - An OpenSSL::X509::Certificate object as client certificate + # * :ca_file - Path to a CA certification file in PEM format. The file can contrain several CA certificates. + # * :ca_path - Path of a CA certification directory containing certifications in PEM format. + # * :verify_mode - Flags for server the certification verification at begining of SSL/TLS session. (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable) + # * :verify_callback - The verify callback for the server certification verification. + # * :verify_depth - The maximum depth for the certificate chain verification. + # * :cert_store - OpenSSL::X509::Store to verify peer certificate. + # * :ssl_timeout -The SSL timeout in seconds. + def ssl_options=(opts={}) + @connection = nil + @ssl_options = opts + end + + # Returns the SSL options hash. + def ssl_options + if defined?(@ssl_options) + @ssl_options + elsif superclass != Object && superclass.ssl_options + superclass.ssl_options + end + end + # An instance of ActiveResource::Connection that is the base \connection to the remote service. # The +refresh+ parameter toggles whether or not the \connection is refreshed at every request # or not (defaults to false). def connection(refresh = false) if defined?(@connection) || superclass == Object @connection = Connection.new(site, format) if refresh || @connection.nil? + @connection.proxy = proxy if proxy @connection.user = user if user @connection.password = password if password @connection.timeout = timeout if timeout + @connection.ssl_options = ssl_options if ssl_options @connection else superclass.connection @@ -557,7 +620,7 @@ module ActiveResource response.code.to_i == 200 end # id && !find_single(id, options).nil? - rescue ActiveResource::ResourceNotFound + rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone false end @@ -611,6 +674,11 @@ module ActiveResource site.is_a?(URI) ? site.dup : URI.parse(site) end + # Accepts a URI and creates the proxy URI from that. + def create_proxy_uri_from(proxy) + proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy) + end + # contains a set of the current prefix parameters. def prefix_parameters @prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set @@ -952,7 +1020,13 @@ module ActiveResource case value when Array resource = find_or_create_resource_for_collection(key) - value.map { |attrs| attrs.is_a?(String) ? attrs.dup : resource.new(attrs) } + value.map do |attrs| + if attrs.is_a?(String) || attrs.is_a?(Numeric) + attrs.duplicable? ? attrs.dup : attrs + else + resource.new(attrs) + end + end when Hash resource = find_or_create_resource_for(key) resource.new(value) diff --git a/vendor/rails/activeresource/lib/active_resource/connection.rb b/vendor/rails/activeresource/lib/active_resource/connection.rb index 85103b53..768b8c99 100644 --- a/vendor/rails/activeresource/lib/active_resource/connection.rb +++ b/vendor/rails/activeresource/lib/active_resource/connection.rb @@ -26,6 +26,14 @@ module ActiveResource def to_s; @message ;end end + # Raised when a OpenSSL::SSL::SSLError occurs. + class SSLError < ConnectionError + def initialize(message) + @message = message + end + def to_s; @message ;end + end + # 3xx Redirection class Redirection < ConnectionError # :nodoc: def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end @@ -49,6 +57,9 @@ module ActiveResource # 409 Conflict class ResourceConflict < ClientError; end # :nodoc: + # 410 Gone + class ResourceGone < ClientError; end # :nodoc: + # 5xx Server Error class ServerError < ConnectionError; end # :nodoc: @@ -67,10 +78,11 @@ module ActiveResource HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept', :put => 'Content-Type', :post => 'Content-Type', - :delete => 'Accept' + :delete => 'Accept', + :head => 'Accept' } - attr_reader :site, :user, :password, :timeout + attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options attr_accessor :format class << self @@ -95,7 +107,12 @@ module ActiveResource @password = URI.decode(@site.password) if @site.password end - # Set user for remote service. + # Set the proxy for remote service. + def proxy=(proxy) + @proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy) + end + + # Set the user for remote service. def user=(user) @user = user end @@ -110,6 +127,11 @@ module ActiveResource @timeout = timeout end + # Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'. + def ssl_options=(opts={}) + @ssl_options = opts + end + # Execute a GET request. # Used to get (find) resources. def get(path, headers = {}) @@ -137,7 +159,7 @@ module ActiveResource # Execute a HEAD request. # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers). def head(path, headers = {}) - request(:head, path, build_request_headers(headers)) + request(:head, path, build_request_headers(headers, :head)) end @@ -151,6 +173,8 @@ module ActiveResource handle_response(result) rescue Timeout::Error => e raise TimeoutError.new(e.message) + rescue OpenSSL::SSL::SSLError => e + raise SSLError.new(e.message) end # Handles response and error codes from remote service. @@ -172,6 +196,8 @@ module ActiveResource raise(MethodNotAllowed.new(response)) when 409 raise(ResourceConflict.new(response)) + when 410 + raise(ResourceGone.new(response)) when 422 raise(ResourceInvalid.new(response)) when 401...500 @@ -186,10 +212,49 @@ module ActiveResource # Creates new Net::HTTP instance for communication with # remote service and resources. def http - http = Net::HTTP.new(@site.host, @site.port) - http.use_ssl = @site.is_a?(URI::HTTPS) - http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl - http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used. + configure_http(new_http) + end + + def new_http + if @proxy + Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password) + else + Net::HTTP.new(@site.host, @site.port) + end + end + + def configure_http(http) + http = apply_ssl_options(http) + + # Net::HTTP timeouts default to 60 seconds. + if @timeout + http.open_timeout = @timeout + http.read_timeout = @timeout + end + + http + end + + def apply_ssl_options(http) + return http unless @site.is_a?(URI::HTTPS) + + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + return http unless defined?(@ssl_options) + + http.ca_path = @ssl_options[:ca_path] if @ssl_options[:ca_path] + http.ca_file = @ssl_options[:ca_file] if @ssl_options[:ca_file] + + http.cert = @ssl_options[:cert] if @ssl_options[:cert] + http.key = @ssl_options[:key] if @ssl_options[:key] + + http.cert_store = @ssl_options[:cert_store] if @ssl_options[:cert_store] + http.ssl_timeout = @ssl_options[:ssl_timeout] if @ssl_options[:ssl_timeout] + + http.verify_mode = @ssl_options[:verify_mode] if @ssl_options[:verify_mode] + http.verify_callback = @ssl_options[:verify_callback] if @ssl_options[:verify_callback] + http.verify_depth = @ssl_options[:verify_depth] if @ssl_options[:verify_depth] + http end diff --git a/vendor/rails/activeresource/lib/active_resource/exceptions.rb b/vendor/rails/activeresource/lib/active_resource/exceptions.rb new file mode 100644 index 00000000..0631cdcf --- /dev/null +++ b/vendor/rails/activeresource/lib/active_resource/exceptions.rb @@ -0,0 +1,66 @@ +module ActiveResource + class ConnectionError < StandardError # :nodoc: + attr_reader :response + + def initialize(response, message = nil) + @response = response + @message = message + end + + def to_s + "Failed with #{response.code} #{response.message if response.respond_to?(:message)}" + end + end + + # Raised when a Timeout::Error occurs. + class TimeoutError < ConnectionError + def initialize(message) + @message = message + end + def to_s; @message ;end + end + + # Raised when a OpenSSL::SSL::SSLError occurs. + class SSLError < ConnectionError + def initialize(message) + @message = message + end + def to_s; @message ;end + end + + # 3xx Redirection + class Redirection < ConnectionError # :nodoc: + def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end + end + + # 4xx Client Error + class ClientError < ConnectionError; end # :nodoc: + + # 400 Bad Request + class BadRequest < ClientError; end # :nodoc + + # 401 Unauthorized + class UnauthorizedAccess < ClientError; end # :nodoc + + # 403 Forbidden + class ForbiddenAccess < ClientError; end # :nodoc + + # 404 Not Found + class ResourceNotFound < ClientError; end # :nodoc: + + # 409 Conflict + class ResourceConflict < ClientError; end # :nodoc: + + # 410 Gone + class ResourceGone < ClientError; end # :nodoc: + + # 5xx Server Error + class ServerError < ConnectionError; end # :nodoc: + + # 405 Method Not Allowed + class MethodNotAllowed < ClientError # :nodoc: + def allowed_methods + @response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym } + end + end +end diff --git a/vendor/rails/activeresource/lib/active_resource/validations.rb b/vendor/rails/activeresource/lib/active_resource/validations.rb index de333993..ee377acb 100644 --- a/vendor/rails/activeresource/lib/active_resource/validations.rb +++ b/vendor/rails/activeresource/lib/active_resource/validations.rb @@ -199,11 +199,10 @@ module ActiveResource alias_method :count, :size alias_method :length, :size - # Grabs errors from the XML response. - def from_xml(xml) + # Grabs errors from an array of messages (like ActiveRecord::Validations) + def from_array(messages) clear humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) } - messages = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue [] messages.each do |message| attr_message = humanized_attributes.keys.detect do |attr_name| if message[0, attr_name.size + 1] == "#{attr_name} " @@ -214,6 +213,18 @@ module ActiveResource add_to_base message if attr_message.nil? end end + + # Grabs errors from the json response. + def from_json(json) + array = ActiveSupport::JSON.decode(json)['errors'] rescue [] + from_array array + end + + # Grabs errors from the XML response. + def from_xml(xml) + array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue [] + from_array array + end end # Module to support validation and errors with Active Resource objects. The module overrides @@ -248,7 +259,12 @@ module ActiveResource save_without_validation true rescue ResourceInvalid => error - errors.from_xml(error.response.body) + case error.response['Content-Type'] + when 'application/xml' + errors.from_xml(error.response.body) + when 'application/json' + errors.from_json(error.response.body) + end false end diff --git a/vendor/rails/activeresource/lib/active_resource/version.rb b/vendor/rails/activeresource/lib/active_resource/version.rb index 8c68de74..6499f32d 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 = 3 + TINY = 4 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/activeresource/test/base/load_test.rb b/vendor/rails/activeresource/test/base/load_test.rb index a475fab3..079ec2c1 100644 --- a/vendor/rails/activeresource/test/base/load_test.rb +++ b/vendor/rails/activeresource/test/base/load_test.rb @@ -49,7 +49,9 @@ class BaseLoadTest < Test::Unit::TestCase :id => 1, :state => { :id => 1, :name => 'Oregon', :notable_rivers => [ { :id => 1, :name => 'Willamette' }, - { :id => 2, :name => 'Columbia', :rafted_by => @matz }] }}} + { :id => 2, :name => 'Columbia', :rafted_by => @matz }], + :postal_codes => [ 97018, 1234567890 ], + :places => [ "Columbia City", "Unknown" ]}}} @person = Person.new end @@ -125,6 +127,19 @@ class BaseLoadTest < Test::Unit::TestCase assert_kind_of Person::Street::State::NotableRiver, rivers.first assert_equal @deep[:street][:state][:notable_rivers].first[:id], rivers.first.id assert_equal @matz[:id], rivers.last.rafted_by.id + + postal_codes = state.postal_codes + assert_kind_of Array, postal_codes + assert_equal 2, postal_codes.size + assert_kind_of Fixnum, postal_codes.first + assert_equal @deep[:street][:state][:postal_codes].first, postal_codes.first + assert_kind_of Numeric, postal_codes.last + assert_equal @deep[:street][:state][:postal_codes].last, postal_codes.last + + places = state.places + assert_kind_of Array, places + assert_kind_of String, places.first + assert_equal @deep[:street][:state][:places].first, places.first end def test_nested_collections_within_the_same_namespace diff --git a/vendor/rails/activeresource/test/base_errors_test.rb b/vendor/rails/activeresource/test/base_errors_test.rb index 7ae92c7d..c1b48671 100644 --- a/vendor/rails/activeresource/test/base_errors_test.rb +++ b/vendor/rails/activeresource/test/base_errors_test.rb @@ -4,45 +4,82 @@ require "fixtures/person" class BaseErrorsTest < Test::Unit::TestCase def setup ActiveResource::HttpMock.respond_to do |mock| - mock.post "/people.xml", {}, "Age can't be blankName can't be blankName must start with a letterPerson quota full for today.", 422 + mock.post "/people.xml", {}, %q(Age can't be blankName can't be blankName must start with a letterPerson quota full for today.), 422, {'Content-Type' => 'application/xml'} + mock.post "/people.json", {}, %q({"errors":["Age can't be blank","Name can't be blank","Name must start with a letter","Person quota full for today."]}), 422, {'Content-Type' => 'application/json'} end @person = Person.new(:name => '', :age => '') assert_equal @person.save, false end def test_should_mark_as_invalid - assert !@person.valid? + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + assert !@person.valid? + end + end end def test_should_parse_xml_errors - assert_kind_of ActiveResource::Errors, @person.errors - assert_equal 4, @person.errors.size + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + assert_kind_of ActiveResource::Errors, @person.errors + assert_equal 4, @person.errors.size + end + end end def test_should_parse_errors_to_individual_attributes - assert @person.errors.invalid?(:name) - assert_equal "can't be blank", @person.errors.on(:age) - assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name] - assert_equal "Person quota full for today.", @person.errors.on_base + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + assert @person.errors[:name].any? + assert_equal "can't be blank", @person.errors[:age] + assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name] + assert_equal "Person quota full for today.", @person.errors[:base] + end + end end def test_should_iterate_over_errors - errors = [] - @person.errors.each { |attribute, message| errors << [attribute, message] } - assert errors.include?(["name", "can't be blank"]) + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + errors = [] + @person.errors.each { |attribute, message| errors << [attribute, message] } + assert errors.include?(['name', "can't be blank"]) + end + end end def test_should_iterate_over_full_errors - errors = [] - @person.errors.each_full { |message| errors << message } - assert errors.include?("Name can't be blank") + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + errors = [] + @person.errors.to_a.each { |message| errors << message } + assert errors.include?(["name", "can't be blank"]) + end + end end def test_should_format_full_errors - full = @person.errors.full_messages - assert full.include?("Age can't be blank") - assert full.include?("Name can't be blank") - assert full.include?("Name must start with a letter") - assert full.include?("Person quota full for today.") + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + full = @person.errors.full_messages + assert full.include?("Age can't be blank") + assert full.include?("Name can't be blank") + assert full.include?("Name must start with a letter") + assert full.include?("Person quota full for today.") + end + end + end + + private + def invalid_user_using_format(mime_type_reference) + previous_format = Person.format + Person.format = mime_type_reference + @person = Person.new(:name => '', :age => '') + assert_equal false, @person.save + + yield + ensure + Person.format = previous_format end end diff --git a/vendor/rails/activeresource/test/base_test.rb b/vendor/rails/activeresource/test/base_test.rb index 6ed6f1a4..527e099a 100644 --- a/vendor/rails/activeresource/test/base_test.rb +++ b/vendor/rails/activeresource/test/base_test.rb @@ -3,6 +3,7 @@ require "fixtures/person" require "fixtures/customer" require "fixtures/street_address" require "fixtures/beast" +require "fixtures/proxy" class BaseTest < Test::Unit::TestCase def setup @@ -124,6 +125,28 @@ class BaseTest < Test::Unit::TestCase assert_nil actor.site end + def test_proxy_accessor_accepts_uri_or_string_argument + proxy = URI.parse('http://localhost') + + assert_nothing_raised { Person.proxy = 'http://localhost' } + assert_equal proxy, Person.proxy + + assert_nothing_raised { Person.proxy = proxy } + assert_equal proxy, Person.proxy + end + + def test_should_use_proxy_prefix_and_credentials + assert_equal 'http://user:password@proxy.local:3000', ProxyResource.proxy.to_s + end + + def test_proxy_variable_can_be_reset + actor = Class.new(ActiveResource::Base) + assert_nil actor.site + actor.proxy = 'http://localhost:31337' + actor.proxy = nil + assert_nil actor.site + end + def test_should_accept_setting_user Forum.user = 'david' assert_equal('david', Forum.user) @@ -142,6 +165,13 @@ class BaseTest < Test::Unit::TestCase assert_equal(5, Forum.connection.timeout) end + def test_should_accept_setting_ssl_options + expected = {:verify => 1} + Forum.ssl_options= expected + assert_equal(expected, Forum.ssl_options) + assert_equal(expected, Forum.connection.ssl_options) + end + def test_user_variable_can_be_reset actor = Class.new(ActiveResource::Base) actor.site = 'http://cinema' @@ -172,6 +202,16 @@ class BaseTest < Test::Unit::TestCase assert_nil actor.connection.timeout end + def test_ssl_options_hash_can_be_reset + actor = Class.new(ActiveResource::Base) + actor.site = 'https://cinema' + assert_nil actor.ssl_options + actor.ssl_options = {:foo => 5} + actor.ssl_options = nil + assert_nil actor.ssl_options + assert_nil actor.connection.ssl_options + end + def test_credentials_from_site_are_decoded actor = Class.new(ActiveResource::Base) actor.site = 'http://my%40email.com:%31%32%33@cinema' @@ -220,6 +260,47 @@ class BaseTest < Test::Unit::TestCase assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class' end + def test_proxy_reader_uses_superclass_site_until_written + # Superclass is Object so returns nil. + assert_nil ActiveResource::Base.proxy + assert_nil Class.new(ActiveResource::Base).proxy + + # Subclass uses superclass proxy. + actor = Class.new(Person) + assert_equal Person.proxy, actor.proxy + + # Subclass returns frozen superclass copy. + assert !Person.proxy.frozen? + assert actor.proxy.frozen? + + # Changing subclass proxy doesn't change superclass site. + actor.proxy = 'http://localhost:31337' + assert_not_equal Person.proxy, actor.proxy + + # Changed subclass proxy is not frozen. + assert !actor.proxy.frozen? + + # Changing superclass proxy doesn't overwrite subclass site. + Person.proxy = 'http://somewhere.else' + assert_not_equal Person.proxy, actor.proxy + + # Changing superclass proxy after subclassing changes subclass site. + jester = Class.new(actor) + actor.proxy = 'http://nomad' + assert_equal actor.proxy, jester.proxy + assert jester.proxy.frozen? + + # Subclasses are always equal to superclass proxy when not overridden + fruit = Class.new(ActiveResource::Base) + apple = Class.new(fruit) + + fruit.proxy = 'http://market' + assert_equal fruit.proxy, apple.proxy, 'subclass did not adopt changes from parent class' + + fruit.proxy = 'http://supermarket' + assert_equal fruit.proxy, apple.proxy, 'subclass did not adopt changes from parent class' + end + def test_user_reader_uses_superclass_user_until_written # Superclass is Object so returns nil. assert_nil ActiveResource::Base.user @@ -330,6 +411,40 @@ class BaseTest < Test::Unit::TestCase assert_equal fruit.timeout, apple.timeout, 'subclass did not adopt changes from parent class' end + def test_ssl_options_reader_uses_superclass_ssl_options_until_written + # Superclass is Object so returns nil. + assert_nil ActiveResource::Base.ssl_options + assert_nil Class.new(ActiveResource::Base).ssl_options + Person.ssl_options = {:foo => 'bar'} + + # Subclass uses superclass ssl_options. + actor = Class.new(Person) + assert_equal Person.ssl_options, actor.ssl_options + + # Changing subclass ssl_options doesn't change superclass ssl_options. + actor.ssl_options = {:baz => ''} + assert_not_equal Person.ssl_options, actor.ssl_options + + # Changing superclass ssl_options doesn't overwrite subclass ssl_options. + Person.ssl_options = {:color => 'blue'} + assert_not_equal Person.ssl_options, actor.ssl_options + + # Changing superclass ssl_options after subclassing changes subclass ssl_options. + jester = Class.new(actor) + actor.ssl_options = {:color => 'red'} + assert_equal actor.ssl_options, jester.ssl_options + + # Subclasses are always equal to superclass ssl_options when not overridden. + fruit = Class.new(ActiveResource::Base) + apple = Class.new(fruit) + + fruit.ssl_options = {:alpha => 'betas'} + assert_equal fruit.ssl_options, apple.ssl_options, 'subclass did not adopt changes from parent class' + + fruit.ssl_options = {:omega => 'moos'} + assert_equal fruit.ssl_options, apple.ssl_options, 'subclass did not adopt changes from parent class' + end + def test_updating_baseclass_site_object_wipes_descendent_cached_connection_objects # Subclasses are always equal to superclass site when not overridden fruit = Class.new(ActiveResource::Base) @@ -783,6 +898,14 @@ class BaseTest < Test::Unit::TestCase assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) } end + def test_destroy_with_410_gone + assert Person.find(1).destroy + ActiveResource::HttpMock.respond_to do |mock| + mock.get "/people/1.xml", {}, nil, 410 + end + assert_raise(ActiveResource::ResourceGone) { Person.find(1).destroy } + end + def test_delete assert Person.delete(1) ActiveResource::HttpMock.respond_to do |mock| @@ -798,6 +921,14 @@ class BaseTest < Test::Unit::TestCase end assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) } end + + def test_delete_with_410_gone + assert Person.delete(1) + ActiveResource::HttpMock.respond_to do |mock| + mock.get "/people/1.xml", {}, nil, 410 + end + assert_raise(ActiveResource::ResourceGone) { Person.find(1) } + end def test_exists # Class method. @@ -850,6 +981,22 @@ class BaseTest < Test::Unit::TestCase end end + def test_exists_without_http_mock + http = Net::HTTP.new(Person.site.host, Person.site.port) + ActiveResource::Connection.any_instance.expects(:http).returns(http) + http.expects(:request).returns(ActiveResource::Response.new("")) + + assert Person.exists?('not-mocked') + end + + def test_exists_with_410_gone + ActiveResource::HttpMock.respond_to do |mock| + mock.head "/people/1.xml", {}, nil, 410 + end + + assert !Person.exists?(1) + end + def test_to_xml matz = Person.find(1) xml = matz.encode diff --git a/vendor/rails/activeresource/test/connection_test.rb b/vendor/rails/activeresource/test/connection_test.rb index 831fbc40..d7466c65 100644 --- a/vendor/rails/activeresource/test/connection_test.rb +++ b/vendor/rails/activeresource/test/connection_test.rb @@ -56,6 +56,9 @@ class ConnectionTest < Test::Unit::TestCase # 409 is an optimistic locking error assert_response_raises ActiveResource::ResourceConflict, 409 + # 410 is a removed resource + assert_response_raises ActiveResource::ResourceGone, 410 + # 422 is a validation error assert_response_raises ActiveResource::ResourceInvalid, 422 @@ -101,6 +104,16 @@ class ConnectionTest < Test::Unit::TestCase assert_equal site, @conn.site end + def test_proxy_accessor_accepts_uri_or_string_argument + proxy = URI.parse("http://proxy_user:proxy_password@proxy.local:4242") + + assert_nothing_raised { @conn.proxy = "http://proxy_user:proxy_password@proxy.local:4242" } + assert_equal proxy, @conn.proxy + + assert_nothing_raised { @conn.proxy = proxy } + assert_equal proxy, @conn.proxy + end + def test_timeout_accessor @conn.timeout = 5 assert_equal 5, @conn.timeout @@ -175,6 +188,17 @@ class ConnectionTest < Test::Unit::TestCase assert_raise(ActiveResource::TimeoutError) { @conn.get('/people_timeout.xml') } end + def test_setting_timeout + http = Net::HTTP.new('') + + [10, 20].each do |timeout| + @conn.timeout = timeout + @conn.send(:configure_http, http) + assert_equal timeout, http.open_timeout + assert_equal timeout, http.read_timeout + end + end + def test_accept_http_header @http = mock('new Net::HTTP') @conn.expects(:http).returns(@http) @@ -183,6 +207,24 @@ class ConnectionTest < Test::Unit::TestCase assert_nothing_raised(Mocha::ExpectationError) { @conn.get(path, {'Accept' => 'application/xhtml+xml'}) } end + def test_ssl_options_get_applied_to_http + http = Net::HTTP.new('') + @conn.site="https://secure" + @conn.ssl_options={:verify_mode => OpenSSL::SSL::VERIFY_PEER} + @conn.timeout = 10 # prevent warning about uninitialized. + @conn.send(:configure_http, http) + + assert http.use_ssl? + assert_equal http.verify_mode, OpenSSL::SSL::VERIFY_PEER + end + + def test_ssl_error + http = Net::HTTP.new('') + @conn.expects(:http).returns(http) + http.expects(:get).raises(OpenSSL::SSL::SSLError, 'Expired certificate') + assert_raise(ActiveResource::SSLError) { @conn.get('/people/1.xml') } + end + protected def assert_response_raises(klass, code) assert_raise(klass, "Expected response code #{code} to raise #{klass}") do diff --git a/vendor/rails/activeresource/test/fixtures/proxy.rb b/vendor/rails/activeresource/test/fixtures/proxy.rb new file mode 100644 index 00000000..bb8e015d --- /dev/null +++ b/vendor/rails/activeresource/test/fixtures/proxy.rb @@ -0,0 +1,4 @@ +class ProxyResource < ActiveResource::Base + self.site = "http://localhost" + self.proxy = "http://user:password@proxy.local:3000" +end \ No newline at end of file diff --git a/vendor/rails/activesupport/CHANGELOG b/vendor/rails/activesupport/CHANGELOG index cb57790d..246f9b8a 100644 --- a/vendor/rails/activesupport/CHANGELOG +++ b/vendor/rails/activesupport/CHANGELOG @@ -1,3 +1,9 @@ +*2.3.4 (September 4, 2009)* + +* Introduce ActiveSupport::Multibyte.clean to clean invalid multibyte strings. + +* Bug fixes + *2.3.3 (July 12, 2009)* * JSON: +Object#to_json+ calls +as_json+ to coerce itself into something natively encodable like +Hash+, +Integer+, or +String+. Override +as_json+ instead of +to_json+ so you're JSON-library-agnostic. [Jeremy Kemper] diff --git a/vendor/rails/activesupport/Rakefile b/vendor/rails/activesupport/Rakefile index ccbab525..d5ece391 100644 --- a/vendor/rails/activesupport/Rakefile +++ b/vendor/rails/activesupport/Rakefile @@ -87,9 +87,6 @@ task :release => [ :package ] do rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages) end - -require 'lib/active_support/values/time_zone' - namespace :tzinfo do desc "Update bundled tzinfo gem. Only copies the subset of classes and definitions required to support Rails time zone features." task :update => ['tzinfo:copy_classes', 'tzinfo:copy_definitions'] do @@ -118,6 +115,8 @@ namespace :tzinfo do end task :copy_definitions => :unpack_gem do + $:.unshift "#{File.dirname(__FILE__)}/lib" + require 'active_support/values/time_zone' definitions_path = "#{destination_path}/tzinfo/definitions/" mkdir_p definitions_path ActiveSupport::TimeZone::MAPPING.values.each do |zone| diff --git a/vendor/rails/activesupport/lib/active_support/all.rb b/vendor/rails/activesupport/lib/active_support/all.rb new file mode 100644 index 00000000..8a8805ee --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/all.rb @@ -0,0 +1,8 @@ +# For forward compatibility with Rails 3. +# +# require 'active_support' loads a very bare minumum in Rails 3. +# require 'active_support/all' loads the whole suite like Rails 2 did. +# +# To prepare for Rails 3, switch to require 'active_support/all' now. + +require 'active_support' diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/array/conversions.rb b/vendor/rails/activesupport/lib/active_support/core_ext/array/conversions.rb index ba8e022f..779743bc 100644 --- a/vendor/rails/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/vendor/rails/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -163,6 +163,7 @@ module ActiveSupport #:nodoc: raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml } require 'builder' unless defined?(Builder) + options = options.dup options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? first.class.to_s.underscore.pluralize : "records" options[:children] ||= options[:root].singularize options[:indent] ||= 2 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 7f94da01..fa7f618e 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 @@ -92,6 +92,7 @@ module ActiveSupport #:nodoc: # Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with # any of these keys: :years, :months, :weeks, :days. def advance(options) + options = options.dup d = self d = d >> options.delete(:years) * 12 if options[:years] d = d >> options.delete(:months) if options[:months] diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/enumerable.rb b/vendor/rails/activesupport/lib/active_support/core_ext/enumerable.rb index a7eaccfe..2c5f59b8 100644 --- a/vendor/rails/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/vendor/rails/activesupport/lib/active_support/core_ext/enumerable.rb @@ -55,12 +55,10 @@ module Enumerable # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) # def sum(identity = 0, &block) - return identity unless size > 0 - if block_given? - map(&block).sum + map(&block).sum(identity) else - inject { |sum, element| sum + element } + inject { |sum, element| sum + element } || identity end end 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 5ae43c0c..a43763f3 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 @@ -99,6 +99,7 @@ module ActiveSupport #:nodoc: def to_xml(options = {}) require 'builder' unless defined?(Builder) + options = options.dup options[:indent] ||= 2 options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]), :root => "hash" }) diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/module/delegation.rb b/vendor/rails/activesupport/lib/active_support/core_ext/module/delegation.rb index 9377bff2..d7796fcd 100644 --- a/vendor/rails/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/vendor/rails/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -120,10 +120,15 @@ class Module end module_eval(<<-EOS, file, line) - def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block) - #{on_nil} if #{to}.nil? - #{to}.__send__(#{method.inspect}, *args, &block) # client && client.__send__(:name, *args, &block) - end # end + def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block) + #{to}.__send__(#{method.inspect}, *args, &block) # client.__send__(:name, *args, &block) + rescue NoMethodError # rescue NoMethodError + if #{to}.nil? # if client.nil? + #{on_nil} + else # else + raise # raise + end # end + end # end EOS end end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/string.rb b/vendor/rails/activesupport/lib/active_support/core_ext/string.rb index 16c544a5..2600f84e 100644 --- a/vendor/rails/activesupport/lib/active_support/core_ext/string.rb +++ b/vendor/rails/activesupport/lib/active_support/core_ext/string.rb @@ -1,6 +1,7 @@ # encoding: utf-8 require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/string/bytesize' require 'active_support/core_ext/string/conversions' require 'active_support/core_ext/string/access' require 'active_support/core_ext/string/starts_ends_with' diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/string/bytesize.rb b/vendor/rails/activesupport/lib/active_support/core_ext/string/bytesize.rb new file mode 100644 index 00000000..ed051b92 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/string/bytesize.rb @@ -0,0 +1,5 @@ +unless '1.9'.respond_to?(:bytesize) + class String + alias :bytesize :size + end +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/time/calculations.rb b/vendor/rails/activesupport/lib/active_support/core_ext/time/calculations.rb index 5ed750af..dfc48f48 100644 --- a/vendor/rails/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/vendor/rails/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -207,9 +207,9 @@ module ActiveSupport #:nodoc: alias :at_midnight :beginning_of_day alias :at_beginning_of_day :beginning_of_day - # Returns a new Time representing the end of the day (23:59:59) + # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9) def end_of_day - change(:hour => 23, :min => 59, :sec => 59) + change(:hour => 23, :min => 59, :sec => 59, :usec => 999999.999) end # Returns a new Time representing the start of the month (1st of the month, 0:00) @@ -219,11 +219,11 @@ module ActiveSupport #:nodoc: end alias :at_beginning_of_month :beginning_of_month - # Returns a new Time representing the end of the month (last day of the month, 0:00) + # Returns a new Time representing the end of the month (end of the last day of the month) def end_of_month #self - ((self.mday-1).days + self.seconds_since_midnight) last_day = ::Time.days_in_month( self.month, self.year ) - change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 0) + change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999) end alias :at_end_of_month :end_of_month @@ -233,7 +233,7 @@ module ActiveSupport #:nodoc: end alias :at_beginning_of_quarter :beginning_of_quarter - # Returns a new Time representing the end of the quarter (last day of march, june, september, december, 23:59:59) + # Returns a new Time representing the end of the quarter (end of the last day of march, june, september, december) def end_of_quarter beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month end @@ -245,9 +245,9 @@ module ActiveSupport #:nodoc: end alias :at_beginning_of_year :beginning_of_year - # Returns a new Time representing the end of the year (31st of december, 23:59:59) + # Returns a new Time representing the end of the year (end of the 31st of december) def end_of_year - change(:month => 12,:day => 31,:hour => 23, :min => 59, :sec => 59) + change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999) end alias :at_end_of_year :end_of_year diff --git a/vendor/rails/activesupport/lib/active_support/deprecation.rb b/vendor/rails/activesupport/lib/active_support/deprecation.rb index d2015166..540be29b 100644 --- a/vendor/rails/activesupport/lib/active_support/deprecation.rb +++ b/vendor/rails/activesupport/lib/active_support/deprecation.rb @@ -90,15 +90,15 @@ module ActiveSupport method_names.each do |method_name| alias_method_chain(method_name, :deprecation) do |target, punctuation| class_eval(<<-EOS, __FILE__, __LINE__) - def #{target}_with_deprecation#{punctuation}(*args, &block) # def generate_secret_with_deprecation(*args, &block) - ::ActiveSupport::Deprecation.warn( # ::ActiveSupport::Deprecation.warn( - self.class.deprecated_method_warning( # self.class.deprecated_method_warning( - :#{method_name}, # :generate_secret, - #{options[method_name].inspect}), # "You should use ActiveSupport::SecureRandom.hex(64)"), - caller # caller - ) # ) - #{target}_without_deprecation#{punctuation}(*args, &block) # generate_secret_without_deprecation(*args, &block) - end # end + def #{target}_with_deprecation#{punctuation}(*args, &block) # def generate_secret_with_deprecation(*args, &block) + ::ActiveSupport::Deprecation.warn( # ::ActiveSupport::Deprecation.warn( + self.class.deprecated_method_warning( # self.class.deprecated_method_warning( + :#{method_name}, # :generate_secret, + #{options[method_name].inspect}), # "You should use ActiveSupport::SecureRandom.hex(64)"), + caller # caller + ) # ) + send(:#{target}_without_deprecation#{punctuation}, *args, &block) # send(:generate_secret_without_deprecation, *args, &block) + end # end EOS end end diff --git a/vendor/rails/activesupport/lib/active_support/json/backends/yaml.rb b/vendor/rails/activesupport/lib/active_support/json/backends/yaml.rb index a2797ccd..8c5b6970 100644 --- a/vendor/rails/activesupport/lib/active_support/json/backends/yaml.rb +++ b/vendor/rails/activesupport/lib/active_support/json/backends/yaml.rb @@ -17,7 +17,7 @@ module ActiveSupport rescue ArgumentError => e raise ParseError, "Invalid JSON string" end - + protected # Ensure that ":" and "," are always followed by a space def convert_json_to_yaml(json) #:nodoc: @@ -41,6 +41,8 @@ module ActiveSupport end when ":","," marks << scanner.pos - 1 unless quoting + when "\\" + scanner.skip(/\\/) end end @@ -82,4 +84,5 @@ module ActiveSupport end end end -end \ No newline at end of file +end + diff --git a/vendor/rails/activesupport/lib/active_support/memoizable.rb b/vendor/rails/activesupport/lib/active_support/memoizable.rb index 71cfe617..9282bab2 100644 --- a/vendor/rails/activesupport/lib/active_support/memoizable.rb +++ b/vendor/rails/activesupport/lib/active_support/memoizable.rb @@ -43,7 +43,7 @@ module ActiveSupport def flush_cache(*syms, &block) syms.each do |sym| - methods.each do |m| + (methods + private_methods + protected_methods).each do |m| if m.to_s =~ /^_unmemoized_(#{sym})/ ivar = ActiveSupport::Memoizable.memoized_ivar_for($1) instance_variable_get(ivar).clear if instance_variable_defined?(ivar) diff --git a/vendor/rails/activesupport/lib/active_support/message_verifier.rb b/vendor/rails/activesupport/lib/active_support/message_verifier.rb index b24acb9f..aae5a341 100644 --- a/vendor/rails/activesupport/lib/active_support/message_verifier.rb +++ b/vendor/rails/activesupport/lib/active_support/message_verifier.rb @@ -25,10 +25,10 @@ module ActiveSupport def verify(signed_message) data, digest = signed_message.split("--") - if digest != generate_digest(data) - raise InvalidSignature - else + if secure_compare(digest, generate_digest(data)) Marshal.load(ActiveSupport::Base64.decode64(data)) + else + raise InvalidSignature end end @@ -38,6 +38,19 @@ module ActiveSupport end private + # constant-time comparison algorithm to prevent timing attacks + def secure_compare(a, b) + if a.length == b.length + result = 0 + for i in 0..(a.length - 1) + result |= a[i] ^ b[i] + end + result == 0 + else + false + end + end + def generate_digest(data) require 'openssl' unless defined?(OpenSSL) OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), @secret, data) diff --git a/vendor/rails/activesupport/lib/active_support/multibyte.rb b/vendor/rails/activesupport/lib/active_support/multibyte.rb index 65a96af4..b6354ee8 100644 --- a/vendor/rails/activesupport/lib/active_support/multibyte.rb +++ b/vendor/rails/activesupport/lib/active_support/multibyte.rb @@ -1,9 +1,5 @@ # encoding: utf-8 -require 'active_support/multibyte/chars' -require 'active_support/multibyte/exceptions' -require 'active_support/multibyte/unicode_database' - module ActiveSupport #:nodoc: module Multibyte # A list of all available normalization forms. See http://www.unicode.org/reports/tr15/tr15-29.html for more @@ -27,7 +23,35 @@ module ActiveSupport #:nodoc: # # Example: # ActiveSupport::Multibyte.proxy_class = CharsForUTF32 - mattr_accessor :proxy_class - self.proxy_class = ActiveSupport::Multibyte::Chars + def self.proxy_class=(klass) + @proxy_class = klass + end + + # Returns the currect proxy class + def self.proxy_class + @proxy_class ||= ActiveSupport::Multibyte::Chars + end + + # Regular expressions that describe valid byte sequences for a character + VALID_CHARACTER = { + # Borrowed from the Kconv library by Shinji KONO - (also as seen on the W3C site) + 'UTF-8' => /\A(?: + [\x00-\x7f] | + [\xc2-\xdf] [\x80-\xbf] | + \xe0 [\xa0-\xbf] [\x80-\xbf] | + [\xe1-\xef] [\x80-\xbf] [\x80-\xbf] | + \xf0 [\x90-\xbf] [\x80-\xbf] [\x80-\xbf] | + [\xf1-\xf3] [\x80-\xbf] [\x80-\xbf] [\x80-\xbf] | + \xf4 [\x80-\x8f] [\x80-\xbf] [\x80-\xbf])\z /xn, + # Quick check for valid Shift-JIS characters, disregards the odd-even pairing + 'Shift_JIS' => /\A(?: + [\x00-\x7e \xa1-\xdf] | + [\x81-\x9f \xe0-\xef] [\x40-\x7e \x80-\x9e \x9f-\xfc])\z /xn + } end end + +require 'active_support/multibyte/chars' +require 'active_support/multibyte/exceptions' +require 'active_support/multibyte/unicode_database' +require 'active_support/multibyte/utils' diff --git a/vendor/rails/activesupport/lib/active_support/multibyte/chars.rb b/vendor/rails/activesupport/lib/active_support/multibyte/chars.rb index 60f082bc..16bc1305 100644 --- a/vendor/rails/activesupport/lib/active_support/multibyte/chars.rb +++ b/vendor/rails/activesupport/lib/active_support/multibyte/chars.rb @@ -73,16 +73,7 @@ module ActiveSupport #:nodoc: UNICODE_TRAILERS_PAT = /(#{codepoints_to_pattern(UNICODE_LEADERS_AND_TRAILERS)})+\Z/ UNICODE_LEADERS_PAT = /\A(#{codepoints_to_pattern(UNICODE_LEADERS_AND_TRAILERS)})+/ - # Borrowed from the Kconv library by Shinji KONO - (also as seen on the W3C site) - UTF8_PAT = /\A(?: - [\x00-\x7f] | - [\xc2-\xdf] [\x80-\xbf] | - \xe0 [\xa0-\xbf] [\x80-\xbf] | - [\xe1-\xef] [\x80-\xbf] [\x80-\xbf] | - \xf0 [\x90-\xbf] [\x80-\xbf] [\x80-\xbf] | - [\xf1-\xf3] [\x80-\xbf] [\x80-\xbf] [\x80-\xbf] | - \xf4 [\x80-\x8f] [\x80-\xbf] [\x80-\xbf] - )*\z/xn + UTF8_PAT = ActiveSupport::Multibyte::VALID_CHARACTER['UTF-8'] attr_reader :wrapped_string alias to_s wrapped_string @@ -205,7 +196,22 @@ module ActiveSupport #:nodoc: # 'Café périferôl'.mb_chars.index('ô') #=> 12 # 'Café périferôl'.mb_chars.index(/\w/u) #=> 0 def index(needle, offset=0) - index = @wrapped_string.index(needle, offset) + wrapped_offset = self.first(offset).wrapped_string.length + index = @wrapped_string.index(needle, wrapped_offset) + index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil + end + + # Returns the position _needle_ in the string, counting in + # codepoints, searching backward from _offset_ or the end of the + # string. Returns +nil+ if _needle_ isn't found. + # + # Example: + # 'Café périferôl'.mb_chars.rindex('é') #=> 6 + # 'Café périferôl'.mb_chars.rindex(/\w/u) #=> 13 + def rindex(needle, offset=nil) + offset ||= length + wrapped_offset = self.first(offset).wrapped_string.length + index = @wrapped_string.rindex(needle, wrapped_offset) index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil end @@ -292,23 +298,23 @@ module ActiveSupport #:nodoc: def rstrip chars(@wrapped_string.gsub(UNICODE_TRAILERS_PAT, '')) end - + # Strips entire range of Unicode whitespace from the left of the string. def lstrip chars(@wrapped_string.gsub(UNICODE_LEADERS_PAT, '')) end - + # Strips entire range of Unicode whitespace from the right and left of the string. def strip rstrip.lstrip end - + # Returns the number of codepoints in the string def size self.class.u_unpack(@wrapped_string).size end alias_method :length, :size - + # Reverses all characters in the string. # # Example: @@ -316,7 +322,7 @@ module ActiveSupport #:nodoc: def reverse chars(self.class.u_unpack(@wrapped_string).reverse.pack('U*')) end - + # Implements Unicode-aware slice with codepoints. Slicing on one point returns the codepoints for that # character. # @@ -631,7 +637,7 @@ module ActiveSupport #:nodoc: string.split(//u).map do |c| c.force_encoding(Encoding::ASCII) if c.respond_to?(:force_encoding) - if !UTF8_PAT.match(c) + if !ActiveSupport::Multibyte::VALID_CHARACTER['UTF-8'].match(c) n = c.unpack('C')[0] n < 128 ? n.chr : n < 160 ? [UCD.cp1252[n] || n].pack('U') : diff --git a/vendor/rails/activesupport/lib/active_support/multibyte/utils.rb b/vendor/rails/activesupport/lib/active_support/multibyte/utils.rb new file mode 100644 index 00000000..acef84da --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/multibyte/utils.rb @@ -0,0 +1,61 @@ +# encoding: utf-8 + +module ActiveSupport #:nodoc: + module Multibyte #:nodoc: + if Kernel.const_defined?(:Encoding) + # Returns a regular expression that matches valid characters in the current encoding + def self.valid_character + VALID_CHARACTER[Encoding.default_internal.to_s] + end + else + def self.valid_character + case $KCODE + when 'UTF8' + VALID_CHARACTER['UTF-8'] + when 'SJIS' + VALID_CHARACTER['Shift_JIS'] + end + end + end + + if 'string'.respond_to?(:valid_encoding?) + # Verifies the encoding of a string + def self.verify(string) + string.valid_encoding? + end + else + def self.verify(string) + if expression = valid_character + for c in string.split(//) + return false unless valid_character.match(c) + end + end + true + end + end + + # Verifies the encoding of the string and raises an exception when it's not valid + def self.verify!(string) + raise EncodingError.new("Found characters with invalid encoding") unless verify(string) + end + + if 'string'.respond_to?(:force_encoding) + # Removes all invalid characters from the string. + # + # Note: this method is a no-op in Ruby 1.9 + def self.clean(string) + string + end + else + def self.clean(string) + if expression = valid_character + stripped = []; for c in string.split(//) + stripped << c if valid_character.match(c) + end; stripped.join + else + string + end + end + end + end +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/test_case.rb b/vendor/rails/activesupport/lib/active_support/test_case.rb index 62fe7f5f..bde74faa 100644 --- a/vendor/rails/activesupport/lib/active_support/test_case.rb +++ b/vendor/rails/activesupport/lib/active_support/test_case.rb @@ -17,7 +17,8 @@ module ActiveSupport class TestCase < ::Test::Unit::TestCase if defined? MiniTest Assertion = MiniTest::Assertion - alias_method :method_name, :name + alias_method :method_name, :name if method_defined? :name + alias_method :method_name, :__name__ if method_defined? :__name__ else # TODO: Figure out how to get the Rails::BacktraceFilter into minitest/unit if defined?(Rails) && ENV['BACKTRACE'].nil? diff --git a/vendor/rails/activesupport/lib/active_support/version.rb b/vendor/rails/activesupport/lib/active_support/version.rb index 24ce690c..5d293365 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 = 3 + TINY = 4 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/activesupport/test/core_ext/array_ext_test.rb b/vendor/rails/activesupport/test/core_ext/array_ext_test.rb index b70ec475..fe48d39a 100644 --- a/vendor/rails/activesupport/test/core_ext/array_ext_test.rb +++ b/vendor/rails/activesupport/test/core_ext/array_ext_test.rb @@ -233,6 +233,13 @@ class ArrayToXmlTests < Test::Unit::TestCase assert xml.include?(%(Jason)), xml end + def test_to_xml_dups_options + options = {:skip_instruct => true} + [].to_xml(options) + # :builder, etc, shouldn't be added to options + assert_equal({:skip_instruct => true}, options) + end + def test_to_xml_with_dedicated_name xml = [ { :name => "David", :age => 26, :age_in_millis => 820497600000 }, { :name => "Jason", :age => 31 } diff --git a/vendor/rails/activesupport/test/core_ext/date_ext_test.rb b/vendor/rails/activesupport/test/core_ext/date_ext_test.rb index 1001868c..2d2bbf10 100644 --- a/vendor/rails/activesupport/test/core_ext/date_ext_test.rb +++ b/vendor/rails/activesupport/test/core_ext/date_ext_test.rb @@ -195,7 +195,7 @@ class DateExtCalculationsTest < Test::Unit::TestCase end def test_end_of_day - assert_equal Time.local(2005,2,21,23,59,59), Date.new(2005,2,21).end_of_day + assert_equal Time.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day end def test_xmlschema @@ -250,6 +250,12 @@ class DateExtCalculationsTest < Test::Unit::TestCase Time.zone_default = nil end + def test_date_advance_should_not_change_passed_options_hash + options = { :years => 3, :months => 11, :days => 2 } + Date.new(2005,2,28).advance(options) + assert_equal({ :years => 3, :months => 11, :days => 2 }, options) + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/vendor/rails/activesupport/test/core_ext/enumerable_test.rb b/vendor/rails/activesupport/test/core_ext/enumerable_test.rb index 92db977a..026c8c8c 100644 --- a/vendor/rails/activesupport/test/core_ext/enumerable_test.rb +++ b/vendor/rails/activesupport/test/core_ext/enumerable_test.rb @@ -58,6 +58,10 @@ class EnumerableTests < Test::Unit::TestCase assert_equal Payment.new(0), [].sum(Payment.new(0)) end + def test_enumerable_sums + assert_equal 10, (1..4).sum + end + def test_each_with_object result = %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } assert_equal({'foo' => 'FOO', 'bar' => 'BAR'}, result) 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 c58c0acb..736c1c1b 100644 --- a/vendor/rails/activesupport/test/core_ext/hash_ext_test.rb +++ b/vendor/rails/activesupport/test/core_ext/hash_ext_test.rb @@ -899,6 +899,13 @@ class HashToXmlTest < Test::Unit::TestCase assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person'] end + def test_to_xml_dups_options + options = {:skip_instruct => true} + {}.to_xml(options) + # :builder, etc, shouldn't be added to options + assert_equal({:skip_instruct => true}, options) + end + def test_datetime_xml_type_with_utc_time alert_xml = <<-XML diff --git a/vendor/rails/activesupport/test/core_ext/module_test.rb b/vendor/rails/activesupport/test/core_ext/module_test.rb index 7fb6e14d..b8974973 100644 --- a/vendor/rails/activesupport/test/core_ext/module_test.rb +++ b/vendor/rails/activesupport/test/core_ext/module_test.rb @@ -31,7 +31,7 @@ end Somewhere = Struct.new(:street, :city) Someone = Struct.new(:name, :place) do - delegate :street, :city, :to => :place + delegate :street, :city, :to_f, :to => :place delegate :state, :to => :@place delegate :upcase, :to => "place.city" end @@ -43,6 +43,7 @@ end Project = Struct.new(:description, :person) do delegate :name, :to => :person, :allow_nil => true + delegate :to_f, :to => :description, :allow_nil => true end class Name @@ -144,6 +145,16 @@ class ModuleTest < Test::Unit::TestCase assert_raise(RuntimeError) { david.street } end + def test_delegation_to_method_that_exists_on_nil + nil_person = Someone.new(nil) + assert_equal 0.0, nil_person.to_f + end + + def test_delegation_to_method_that_exists_on_nil_when_allowing_nil + nil_project = Project.new(nil) + assert_equal 0.0, nil_project.to_f + end + def test_parent assert_equal Yz::Zy, Yz::Zy::Cd.parent assert_equal Yz, Yz::Zy.parent 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 7d51e81f..e57008b7 100644 --- a/vendor/rails/activesupport/test/core_ext/string_ext_test.rb +++ b/vendor/rails/activesupport/test/core_ext/string_ext_test.rb @@ -274,3 +274,10 @@ class CoreExtStringMultibyteTest < ActiveSupport::TestCase end end end + +class StringBytesizeTest < Test::Unit::TestCase + def test_bytesize + assert_respond_to 'foo', :bytesize + assert_equal 3, 'foo'.bytesize + end +end diff --git a/vendor/rails/activesupport/test/core_ext/time_ext_test.rb b/vendor/rails/activesupport/test/core_ext/time_ext_test.rb index c0855520..62c27724 100644 --- a/vendor/rails/activesupport/test/core_ext/time_ext_test.rb +++ b/vendor/rails/activesupport/test/core_ext/time_ext_test.rb @@ -84,45 +84,45 @@ class TimeExtCalculationsTest < Test::Unit::TestCase end def test_end_of_day - assert_equal Time.local(2007,8,12,23,59,59), Time.local(2007,8,12,10,10,10).end_of_day + assert_equal Time.local(2007,8,12,23,59,59,999999.999), Time.local(2007,8,12,10,10,10).end_of_day with_env_tz 'US/Eastern' do - assert_equal Time.local(2007,4,2,23,59,59), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST' - assert_equal Time.local(2007,10,29,23,59,59), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST' + assert_equal Time.local(2007,4,2,23,59,59,999999.999), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST' + assert_equal Time.local(2007,10,29,23,59,59,999999.999), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST' end with_env_tz 'NZ' do - assert_equal Time.local(2006,3,19,23,59,59), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST' - assert_equal Time.local(2006,10,1,23,59,59), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST' + assert_equal Time.local(2006,3,19,23,59,59,999999.999), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST' + assert_equal Time.local(2006,10,1,23,59,59,999999.999), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST' end end def test_end_of_week - assert_equal Time.local(2008,1,6,23,59,59), Time.local(2007,12,31,10,10,10).end_of_week - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,27,0,0,0).end_of_week #monday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,28,0,0,0).end_of_week #tuesday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,29,0,0,0).end_of_week #wednesday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,30,0,0,0).end_of_week #thursday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,31,0,0,0).end_of_week #friday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,9,01,0,0,0).end_of_week #saturday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,9,02,0,0,0).end_of_week #sunday + assert_equal Time.local(2008,1,6,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_week + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,27,0,0,0).end_of_week #monday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,28,0,0,0).end_of_week #tuesday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,29,0,0,0).end_of_week #wednesday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,30,0,0,0).end_of_week #thursday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,31,0,0,0).end_of_week #friday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,01,0,0,0).end_of_week #saturday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,02,0,0,0).end_of_week #sunday end def test_end_of_month - assert_equal Time.local(2005,3,31,23,59,59), Time.local(2005,3,20,10,10,10).end_of_month - assert_equal Time.local(2005,2,28,23,59,59), Time.local(2005,2,20,10,10,10).end_of_month - assert_equal Time.local(2005,4,30,23,59,59), Time.local(2005,4,20,10,10,10).end_of_month + assert_equal Time.local(2005,3,31,23,59,59,999999.999), Time.local(2005,3,20,10,10,10).end_of_month + assert_equal Time.local(2005,2,28,23,59,59,999999.999), Time.local(2005,2,20,10,10,10).end_of_month + assert_equal Time.local(2005,4,30,23,59,59,999999.999), Time.local(2005,4,20,10,10,10).end_of_month end def test_end_of_quarter - assert_equal Time.local(2007,3,31,23,59,59), Time.local(2007,2,15,10,10,10).end_of_quarter - assert_equal Time.local(2007,3,31,23,59,59), Time.local(2007,3,31,0,0,0).end_of_quarter - assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,12,21,10,10,10).end_of_quarter - assert_equal Time.local(2007,6,30,23,59,59), Time.local(2007,4,1,0,0,0).end_of_quarter - assert_equal Time.local(2008,6,30,23,59,59), Time.local(2008,5,31,0,0,0).end_of_quarter + assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,2,15,10,10,10).end_of_quarter + assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,3,31,0,0,0).end_of_quarter + assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,21,10,10,10).end_of_quarter + assert_equal Time.local(2007,6,30,23,59,59,999999.999), Time.local(2007,4,1,0,0,0).end_of_quarter + assert_equal Time.local(2008,6,30,23,59,59,999999.999), Time.local(2008,5,31,0,0,0).end_of_quarter end def test_end_of_year - assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,2,22,10,10,10).end_of_year - assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,12,31,10,10,10).end_of_year + assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,2,22,10,10,10).end_of_year + assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_year end def test_beginning_of_year diff --git a/vendor/rails/activesupport/test/deprecation_test.rb b/vendor/rails/activesupport/test/deprecation_test.rb index 73a1f995..a3ae39d0 100644 --- a/vendor/rails/activesupport/test/deprecation_test.rb +++ b/vendor/rails/activesupport/test/deprecation_test.rb @@ -25,6 +25,9 @@ class Deprecatee def e; end deprecate :a, :b, :c => :e, :d => "you now need to do something extra for this one" + def f=(v); end + deprecate :f= + module B C = 1 end @@ -133,6 +136,7 @@ class DeprecationTest < ActiveSupport::TestCase def test_deprecation_without_explanation assert_deprecated { @dtc.a } assert_deprecated { @dtc.b } + assert_deprecated { @dtc.f = :foo } end def test_deprecation_with_alternate_method diff --git a/vendor/rails/activesupport/test/flush_cache_on_private_memoization_test.rb b/vendor/rails/activesupport/test/flush_cache_on_private_memoization_test.rb new file mode 100644 index 00000000..ddbd05b0 --- /dev/null +++ b/vendor/rails/activesupport/test/flush_cache_on_private_memoization_test.rb @@ -0,0 +1,44 @@ +require 'rubygems' +require 'activesupport' +require 'test/unit' + +class FlashCacheOnPrivateMemoizationTest < Test::Unit::TestCase + extend ActiveSupport::Memoizable + + def test_public + assert_method_unmemoizable :pub + end + + def test_protected + assert_method_unmemoizable :prot + end + + def test_private + assert_method_unmemoizable :priv + end + + def pub; rand end + memoize :pub + + protected + + def prot; rand end + memoize :prot + + private + + def priv; rand end + memoize :priv + + def assert_method_unmemoizable(meth, message=nil) + full_message = build_message(message, " not unmemoizable.\n", meth) + assert_block(full_message) do + a = send meth + b = send meth + unmemoize_all + c = send meth + a == b && a != c + end + end + +end \ No newline at end of file diff --git a/vendor/rails/activesupport/test/json/decoding_test.rb b/vendor/rails/activesupport/test/json/decoding_test.rb index 86053c9f..d0d0ec7a 100644 --- a/vendor/rails/activesupport/test/json/decoding_test.rb +++ b/vendor/rails/activesupport/test/json/decoding_test.rb @@ -12,10 +12,10 @@ class TestJSONDecoding < ActiveSupport::TestCase %({"a": "a's, b's and c's", "b": "5,000"}) => {"a" => "a's, b's and c's", "b" => "5,000"}, # multibyte %({"matzue": "松江", "asakusa": "浅草"}) => {"matzue" => "松江", "asakusa" => "浅草"}, - %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)}, - %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)}, + %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)}, + %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)}, # no time zone - %({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"}, + %({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"}, # needs to be *exact* %({"a": " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "}, %({"a": "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"}, @@ -27,6 +27,7 @@ class TestJSONDecoding < ActiveSupport::TestCase %({"a": null}) => {"a" => nil}, %({"a": true}) => {"a" => true}, %({"a": false}) => {"a" => false}, + %q({"bad":"\\\\","trailing":""}) => {"bad" => "\\", "trailing" => ""}, %q({"a": "http:\/\/test.host\/posts\/1"}) => {"a" => "http://test.host/posts/1"}, %q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => ""}, %q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"}, @@ -74,4 +75,5 @@ class TestJSONDecoding < ActiveSupport::TestCase def test_failed_json_decoding assert_raise(ActiveSupport::JSON::ParseError) { ActiveSupport::JSON.decode(%({: 1})) } end -end \ No newline at end of file +end + diff --git a/vendor/rails/activesupport/test/multibyte_chars_test.rb b/vendor/rails/activesupport/test/multibyte_chars_test.rb index 661b33cc..05eee42d 100644 --- a/vendor/rails/activesupport/test/multibyte_chars_test.rb +++ b/vendor/rails/activesupport/test/multibyte_chars_test.rb @@ -231,7 +231,19 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase assert_nil @chars.index('u') assert_equal 0, @chars.index('こに') assert_equal 2, @chars.index('ち') + assert_equal 2, @chars.index('ち', -2) + assert_equal nil, @chars.index('ち', -1) assert_equal 3, @chars.index('わ') + assert_equal 5, 'ééxééx'.mb_chars.index('x', 4) + end + + def test_rindex_should_return_character_offset + assert_nil @chars.rindex('u') + assert_equal 1, @chars.rindex('に') + assert_equal 2, @chars.rindex('ち', -2) + assert_nil @chars.rindex('ち', -3) + assert_equal 6, 'Café périferôl'.mb_chars.rindex('é') + assert_equal 13, 'Café périferôl'.mb_chars.rindex(/\w/u) end def test_indexed_insert_should_take_character_offsets diff --git a/vendor/rails/activesupport/test/multibyte_utils_test.rb b/vendor/rails/activesupport/test/multibyte_utils_test.rb new file mode 100644 index 00000000..d8ac5ff1 --- /dev/null +++ b/vendor/rails/activesupport/test/multibyte_utils_test.rb @@ -0,0 +1,141 @@ +# encoding: utf-8 + +require 'abstract_unit' +require 'multibyte_test_helpers' + +class MultibyteUtilsTest < ActiveSupport::TestCase + include MultibyteTestHelpers + + test "valid_character returns an expression for the current encoding" do + with_encoding('None') do + assert_nil ActiveSupport::Multibyte.valid_character + end + with_encoding('UTF8') do + assert_equal ActiveSupport::Multibyte::VALID_CHARACTER['UTF-8'], ActiveSupport::Multibyte.valid_character + end + with_encoding('SJIS') do + assert_equal ActiveSupport::Multibyte::VALID_CHARACTER['Shift_JIS'], ActiveSupport::Multibyte.valid_character + end + end + + test "verify verifies ASCII strings are properly encoded" do + with_encoding('None') do + examples.each do |example| + assert ActiveSupport::Multibyte.verify(example) + end + end + end + + test "verify verifies UTF-8 strings are properly encoded" do + with_encoding('UTF8') do + assert ActiveSupport::Multibyte.verify(example('valid UTF-8')) + assert !ActiveSupport::Multibyte.verify(example('invalid UTF-8')) + end + end + + test "verify verifies Shift-JIS strings are properly encoded" do + with_encoding('SJIS') do + assert ActiveSupport::Multibyte.verify(example('valid Shift-JIS')) + assert !ActiveSupport::Multibyte.verify(example('invalid Shift-JIS')) + end + end + + test "verify! raises an exception when it finds an invalid character" do + with_encoding('UTF8') do + assert_raises(ActiveSupport::Multibyte::EncodingError) do + ActiveSupport::Multibyte.verify!(example('invalid UTF-8')) + end + end + end + + test "verify! doesn't raise an exception when the encoding is valid" do + with_encoding('UTF8') do + assert_nothing_raised do + ActiveSupport::Multibyte.verify!(example('valid UTF-8')) + end + end + end + + if RUBY_VERSION < '1.9' + test "clean leaves ASCII strings intact" do + with_encoding('None') do + [ + 'word', "\270\236\010\210\245" + ].each do |string| + assert_equal string, ActiveSupport::Multibyte.clean(string) + end + end + end + + test "clean cleans invalid characters from UTF-8 encoded strings" do + with_encoding('UTF8') do + cleaned_utf8 = [8].pack('C*') + assert_equal example('valid UTF-8'), ActiveSupport::Multibyte.clean(example('valid UTF-8')) + assert_equal cleaned_utf8, ActiveSupport::Multibyte.clean(example('invalid UTF-8')) + end + end + + test "clean cleans invalid characters from Shift-JIS encoded strings" do + with_encoding('SJIS') do + cleaned_sjis = [184, 0, 136, 165].pack('C*') + assert_equal example('valid Shift-JIS'), ActiveSupport::Multibyte.clean(example('valid Shift-JIS')) + assert_equal cleaned_sjis, ActiveSupport::Multibyte.clean(example('invalid Shift-JIS')) + end + end + else + test "clean is a no-op" do + with_encoding('UTF8') do + assert_equal example('invalid Shift-JIS'), ActiveSupport::Multibyte.clean(example('invalid Shift-JIS')) + end + end + end + + private + + STRINGS = { + 'valid ASCII' => [65, 83, 67, 73, 73].pack('C*'), + 'invalid ASCII' => [128].pack('C*'), + 'valid UTF-8' => [227, 129, 147, 227, 129, 171, 227, 129, 161, 227, 130, 143].pack('C*'), + 'invalid UTF-8' => [184, 158, 8, 136, 165].pack('C*'), + 'valid Shift-JIS' => [131, 122, 129, 91, 131, 128].pack('C*'), + 'invalid Shift-JIS' => [184, 158, 8, 0, 255, 136, 165].pack('C*') + } + + if Kernel.const_defined?(:Encoding) + def example(key) + STRINGS[key].force_encoding(Encoding.default_internal) + end + + def examples + STRINGS.values.map { |s| s.force_encoding(Encoding.default_internal) } + end + else + def example(key) + STRINGS[key] + end + + def examples + STRINGS.values + end + end + + if 'string'.respond_to?(:encoding) + def with_encoding(enc) + before = Encoding.default_internal + + case enc + when 'UTF8' + Encoding.default_internal = Encoding::UTF_8 + when 'SJIS' + Encoding.default_internal = Encoding::Shift_JIS + else + Encoding.default_internal = Encoding::BINARY + end + yield + + Encoding.default_internal = before + end + else + alias with_encoding with_kcode + end +end \ No newline at end of file diff --git a/vendor/rails/ci/cruise_config.rb b/vendor/rails/ci/cruise_config.rb index 325c2139..9c7fa98a 100644 --- a/vendor/rails/ci/cruise_config.rb +++ b/vendor/rails/ci/cruise_config.rb @@ -1,6 +1,9 @@ Project.configure do |project| - project.build_command = 'ruby ci/ci_build.rb' -# project.email_notifier.emails = ['thewoolleyman@gmail.com'] - project.email_notifier.emails = ['thewoolleyman@gmail.com','michael@koziarski.com', 'david@loudthinking.com', 'jeremy@bitsweat.net', 'josh@joshpeek.com', 'pratiknaik@gmail.com', 'wycats@gmail.com'] - project.email_notifier.from = 'thewoolleyman+railsci@gmail.com' + # Send email notifications about broken and fixed builds to core mailing list + if Socket.gethostname =~ /ci.rubyonrails.org/ && ENV['ENABLE_RAILS_CI_EMAILS'] == 'true' + project.email_notifier.emails = ['rubyonrails-core@googlegroups.com'] + end + + project.build_command = 'sudo gem update --system && ruby ci/ci_build.rb' + project.email_notifier.from = 'thewoolleyman@gmail.com' end diff --git a/vendor/rails/ci/geminstaller.yml b/vendor/rails/ci/geminstaller.yml index a04bf944..39e3fc91 100644 --- a/vendor/rails/ci/geminstaller.yml +++ b/vendor/rails/ci/geminstaller.yml @@ -12,7 +12,7 @@ gems: #version: >= 2.7 version: = 2.7 - name: pg - version: >= 0.7.9.2008.10.13 + version: >= 0.8.0 - name: rack version: '~> 1.0.0' - name: rake diff --git a/vendor/rails/railties/CHANGELOG b/vendor/rails/railties/CHANGELOG index b095fab8..fc28afe2 100644 --- a/vendor/rails/railties/CHANGELOG +++ b/vendor/rails/railties/CHANGELOG @@ -1,7 +1,13 @@ +*2.3.4 (September 4, 2009)* + +* I18n support for plugins. #2325 [Antonio Tapiador, Sven Fuchs] + + *2.3.3 (July 12 2009) * Version bump + *2.3.2 [Final] (March 15, 2009)* * Allow metal to live in plugins #2045 [Matthew Rudy] diff --git a/vendor/rails/railties/Rakefile b/vendor/rails/railties/Rakefile index 9bdb3372..b7c199f4 100644 --- a/vendor/rails/railties/Rakefile +++ b/vendor/rails/railties/Rakefile @@ -199,11 +199,14 @@ task :copy_configs do cp "configs/locales/en.yml", "#{PKG_DESTINATION}/config/locales/en.yml" + cp "configs/seeds.rb", "#{PKG_DESTINATION}/db/seeds.rb" + cp "environments/boot.rb", "#{PKG_DESTINATION}/config/boot.rb" cp "environments/environment.rb", "#{PKG_DESTINATION}/config/environment.rb" cp "environments/production.rb", "#{PKG_DESTINATION}/config/environments/production.rb" cp "environments/development.rb", "#{PKG_DESTINATION}/config/environments/development.rb" cp "environments/test.rb", "#{PKG_DESTINATION}/config/environments/test.rb" + end task :copy_binfiles do @@ -311,11 +314,11 @@ spec = Gem::Specification.new do |s| EOF s.add_dependency('rake', '>= 0.8.3') - s.add_dependency('activesupport', '= 2.3.3' + PKG_BUILD) - s.add_dependency('activerecord', '= 2.3.3' + PKG_BUILD) - s.add_dependency('actionpack', '= 2.3.3' + PKG_BUILD) - s.add_dependency('actionmailer', '= 2.3.3' + PKG_BUILD) - s.add_dependency('activeresource', '= 2.3.3' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.4' + PKG_BUILD) + s.add_dependency('activerecord', '= 2.3.4' + PKG_BUILD) + s.add_dependency('actionpack', '= 2.3.4' + PKG_BUILD) + s.add_dependency('actionmailer', '= 2.3.4' + PKG_BUILD) + s.add_dependency('activeresource', '= 2.3.4' + PKG_BUILD) s.rdoc_options << '--exclude' << '.' s.has_rdoc = false diff --git a/vendor/rails/railties/bin/about b/vendor/rails/railties/bin/about index ed8deb0d..1eeb6eb9 100755 --- a/vendor/rails/railties/bin/about +++ b/vendor/rails/railties/bin/about @@ -1,4 +1,4 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot' +require File.expand_path('../../config/boot', __FILE__) $LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info" -require 'commands/about' \ No newline at end of file +require 'commands/about' diff --git a/vendor/rails/railties/bin/console b/vendor/rails/railties/bin/console index 498077ab..235a1f27 100755 --- a/vendor/rails/railties/bin/console +++ b/vendor/rails/railties/bin/console @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot' +require File.expand_path('../../config/boot', __FILE__) require 'commands/console' diff --git a/vendor/rails/railties/bin/dbconsole b/vendor/rails/railties/bin/dbconsole index caa60ce8..83c8436a 100755 --- a/vendor/rails/railties/bin/dbconsole +++ b/vendor/rails/railties/bin/dbconsole @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot' +require File.expand_path('../../config/boot', __FILE__) require 'commands/dbconsole' diff --git a/vendor/rails/railties/bin/destroy b/vendor/rails/railties/bin/destroy index a4df765a..88d295f7 100755 --- a/vendor/rails/railties/bin/destroy +++ b/vendor/rails/railties/bin/destroy @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot' +require File.expand_path('../../config/boot', __FILE__) require 'commands/destroy' diff --git a/vendor/rails/railties/bin/generate b/vendor/rails/railties/bin/generate index 173a9f14..62a8a4c0 100755 --- a/vendor/rails/railties/bin/generate +++ b/vendor/rails/railties/bin/generate @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot' +require File.expand_path('../../config/boot', __FILE__) require 'commands/generate' diff --git a/vendor/rails/railties/bin/performance/benchmarker b/vendor/rails/railties/bin/performance/benchmarker index c842d35d..3bff809f 100755 --- a/vendor/rails/railties/bin/performance/benchmarker +++ b/vendor/rails/railties/bin/performance/benchmarker @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../config/boot' +require File.expand_path('../../../config/boot', __FILE__) require 'commands/performance/benchmarker' diff --git a/vendor/rails/railties/bin/performance/profiler b/vendor/rails/railties/bin/performance/profiler index d855ac8b..07640575 100755 --- a/vendor/rails/railties/bin/performance/profiler +++ b/vendor/rails/railties/bin/performance/profiler @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../config/boot' +require File.expand_path('../../../config/boot', __FILE__) require 'commands/performance/profiler' diff --git a/vendor/rails/railties/bin/plugin b/vendor/rails/railties/bin/plugin index 87cd2070..b82201fa 100755 --- a/vendor/rails/railties/bin/plugin +++ b/vendor/rails/railties/bin/plugin @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot' +require File.expand_path('../../config/boot', __FILE__) require 'commands/plugin' diff --git a/vendor/rails/railties/bin/runner b/vendor/rails/railties/bin/runner index a4a7cb25..be4c5d45 100755 --- a/vendor/rails/railties/bin/runner +++ b/vendor/rails/railties/bin/runner @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot' +require File.expand_path('../../config/boot', __FILE__) require 'commands/runner' diff --git a/vendor/rails/railties/bin/server b/vendor/rails/railties/bin/server index 3c67f39b..b9fcb717 100755 --- a/vendor/rails/railties/bin/server +++ b/vendor/rails/railties/bin/server @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/boot' +require File.expand_path('../../config/boot', __FILE__) require 'commands/server' diff --git a/vendor/rails/railties/builtin/rails_info/rails/info.rb b/vendor/rails/railties/builtin/rails_info/rails/info.rb index a20d9bfe..8c858d23 100644 --- a/vendor/rails/railties/builtin/rails_info/rails/info.rb +++ b/vendor/rails/railties/builtin/rails_info/rails/info.rb @@ -25,8 +25,10 @@ module Rails end def framework_version(framework) - require "#{framework}/version" - "#{framework.classify}::VERSION::STRING".constantize + if Object.const_defined?(framework.classify) + require "#{framework}/version" + "#{framework.classify}::VERSION::STRING".constantize + end end def edge_rails_revision(info = git_info) diff --git a/vendor/rails/railties/configs/initializers/new_rails_defaults.rb b/vendor/rails/railties/configs/initializers/new_rails_defaults.rb index 8ec3186c..c94db0a6 100644 --- a/vendor/rails/railties/configs/initializers/new_rails_defaults.rb +++ b/vendor/rails/railties/configs/initializers/new_rails_defaults.rb @@ -11,6 +11,8 @@ if defined?(ActiveRecord) ActiveRecord::Base.store_full_sti_class = true end +ActionController::Routing.generate_best_match = false + # Use ISO 8601 format for JSON serialized times and dates. ActiveSupport.use_standard_json_time_format = true diff --git a/vendor/rails/railties/configs/seeds.rb b/vendor/rails/railties/configs/seeds.rb new file mode 100644 index 00000000..3174d0cb --- /dev/null +++ b/vendor/rails/railties/configs/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) +# Major.create(:name => 'Daley', :city => cities.first) diff --git a/vendor/rails/railties/environments/boot.rb b/vendor/rails/railties/environments/boot.rb index 0ad0f787..dd5e3b69 100644 --- a/vendor/rails/railties/environments/boot.rb +++ b/vendor/rails/railties/environments/boot.rb @@ -82,8 +82,8 @@ module Rails end def load_rubygems + min_version = '1.3.2' require 'rubygems' - min_version = '1.3.1' unless rubygems_version >= min_version $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) exit 1 diff --git a/vendor/rails/railties/lib/commands/dbconsole.rb b/vendor/rails/railties/lib/commands/dbconsole.rb index 8002264f..e6f11a45 100644 --- a/vendor/rails/railties/lib/commands/dbconsole.rb +++ b/vendor/rails/railties/lib/commands/dbconsole.rb @@ -33,11 +33,15 @@ end def find_cmd(*commands) dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR) commands += commands.map{|cmd| "#{cmd}.exe"} if RUBY_PLATFORM =~ /win32/ - commands.detect do |cmd| - dirs_on_path.detect do |path| - File.executable? File.join(path, cmd) + + full_path_command = nil + found = commands.detect do |cmd| + dir = dirs_on_path.detect do |path| + full_path_command = File.join(path, cmd) + File.executable? full_path_command end - end || abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.") + end + found ? full_path_command : abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.") end case config["adapter"] diff --git a/vendor/rails/railties/lib/initializer.rb b/vendor/rails/railties/lib/initializer.rb index 5f5e557d..196bd93b 100644 --- a/vendor/rails/railties/lib/initializer.rb +++ b/vendor/rails/railties/lib/initializer.rb @@ -442,7 +442,7 @@ Run `rake gems:install` to install the missing gems. def initialize_database_middleware if configuration.frameworks.include?(:active_record) if configuration.frameworks.include?(:action_controller) && - ActionController::Base.session_store == ActiveRecord::SessionStore + ActionController::Base.session_store.name == 'ActiveRecord::SessionStore' configuration.middleware.insert_before :"ActiveRecord::SessionStore", ActiveRecord::ConnectionAdapters::ConnectionManagement configuration.middleware.insert_before :"ActiveRecord::SessionStore", ActiveRecord::QueryCache else diff --git a/vendor/rails/railties/lib/rails/plugin.rb b/vendor/rails/railties/lib/rails/plugin.rb index 80deb73b..1dc4d142 100644 --- a/vendor/rails/railties/lib/rails/plugin.rb +++ b/vendor/rails/railties/lib/rails/plugin.rb @@ -71,6 +71,10 @@ module Rails File.exist?(routing_file) end + # Returns true if there is any localization file in locale_path + def localized? + locale_files.any? + end def view_path File.join(directory, 'app', 'views') @@ -87,6 +91,14 @@ module Rails def routing_file File.join(directory, 'config', 'routes.rb') end + + def locale_path + File.join(directory, 'config', 'locales') + end + + def locale_files + Dir[ File.join(locale_path, '*.{rb,yml}') ] + end private diff --git a/vendor/rails/railties/lib/rails/plugin/loader.rb b/vendor/rails/railties/lib/rails/plugin/loader.rb index 66e01d70..49670b31 100644 --- a/vendor/rails/railties/lib/rails/plugin/loader.rb +++ b/vendor/rails/railties/lib/rails/plugin/loader.rb @@ -73,6 +73,7 @@ module Rails def configure_engines if engines.any? add_engine_routing_configurations + add_engine_locales add_engine_controller_paths add_engine_view_paths end @@ -84,6 +85,12 @@ module Rails end end + def add_engine_locales + # reverse it such that the last engine can overwrite translations from the first, like with routes + locale_files = engines.select(&:localized?).collect(&:locale_files).reverse.flatten + I18n.load_path += locale_files - I18n.load_path + end + def add_engine_controller_paths ActionController::Routing.controller_paths += engines.collect(&:controller_path) end diff --git a/vendor/rails/railties/lib/rails/version.rb b/vendor/rails/railties/lib/rails/version.rb index d3f81d88..47171d19 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 = 3 + TINY = 4 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/railties/lib/rails_generator/base.rb b/vendor/rails/railties/lib/rails_generator/base.rb index aa7081f8..dd75f05c 100644 --- a/vendor/rails/railties/lib/rails_generator/base.rb +++ b/vendor/rails/railties/lib/rails_generator/base.rb @@ -233,13 +233,13 @@ module Rails base_name, @class_path, @file_path, @class_nesting, @class_nesting_depth = extract_modules(@name) @class_name_without_nesting, @singular_name, @plural_name = inflect_names(base_name) @table_name = (!defined?(ActiveRecord::Base) || ActiveRecord::Base.pluralize_table_names) ? plural_name : singular_name - @table_name.gsub! '/', '_' if @class_nesting.empty? @class_name = @class_name_without_nesting else @table_name = @class_nesting.underscore << "_" << @table_name @class_name = "#{@class_nesting}::#{@class_name_without_nesting}" end + @table_name.gsub! '/', '_' end # Extract modules from filesystem-style or ruby-style path: diff --git a/vendor/rails/railties/lib/rails_generator/generators/applications/app/app_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/applications/app/app_generator.rb index 2c31d895..c8c2239f 100644 --- a/vendor/rails/railties/lib/rails_generator/generators/applications/app/app_generator.rb +++ b/vendor/rails/railties/lib/rails_generator/generators/applications/app/app_generator.rb @@ -125,6 +125,7 @@ class AppGenerator < Rails::Generator::Base create_database_configuration_file(m) create_routes_file(m) create_locale_file(m) + create_seeds_file(m) create_initializer_files(m) create_environment_files(m) end @@ -176,6 +177,10 @@ class AppGenerator < Rails::Generator::Base m.file "configs/routes.rb", "config/routes.rb" end + def create_seeds_file(m) + m.file "configs/seeds.rb", "db/seeds.rb" + end + def create_initializer_files(m) %w( backtrace_silencers diff --git a/vendor/rails/railties/lib/rails_generator/generators/applications/app/scm/git.rb b/vendor/rails/railties/lib/rails_generator/generators/applications/app/scm/git.rb index 445de6ab..a53494de 100644 --- a/vendor/rails/railties/lib/rails_generator/generators/applications/app/scm/git.rb +++ b/vendor/rails/railties/lib/rails_generator/generators/applications/app/scm/git.rb @@ -1,16 +1,18 @@ +STDOUT.sync = true + module Rails class Git < Scm def self.clone(repos, branch=nil) - `git clone #{repos}` + system "git clone #{repos}" if branch - `cd #{repos.split('/').last}/` - `git checkout #{branch}` + system "cd #{repos.split('/').last}/" + system "git checkout #{branch}" end end def self.run(command) - `git #{command}` + system "git #{command}" end end end \ No newline at end of file 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 3b49b1fa..40f21cc4 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 @@ -237,7 +237,7 @@ module Rails # ==== Example # # inside('vendor') do - # run('ln -s ~/edge rails) + # run('ln -s ~/edge rails') # end # def run(command, log_action = true) diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/model/model_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/model/model_generator.rb index 582a2892..1895d8a9 100644 --- a/vendor/rails/railties/lib/rails_generator/generators/components/model/model_generator.rb +++ b/vendor/rails/railties/lib/rails_generator/generators/components/model/model_generator.rb @@ -19,10 +19,17 @@ class ModelGenerator < Rails::Generator::NamedBase m.template 'fixtures.yml', File.join('test/fixtures', "#{table_name}.yml") end + migration_file_path = file_path.gsub(/\//, '_') + migration_name = class_name + if ActiveRecord::Base.pluralize_table_names + migration_name = migration_name.pluralize + migration_file_path = migration_file_path.pluralize + end + unless options[:skip_migration] m.migration_template 'migration.rb', 'db/migrate', :assigns => { - :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}" - }, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}" + :migration_name => "Create#{migration_name.gsub(/::/, '')}" + }, :migration_file_name => "create_#{migration_file_path}" end end end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb index 2a5edeed..88bc3252 100644 --- a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb @@ -19,6 +19,7 @@ class ScaffoldGenerator < Rails::Generator::NamedBase if @name == @name.pluralize && !options[:force_plural] logger.warning "Plural version of the model detected, using singularized version. Override with --force-plural." @name = @name.singularize + assign_names!(@name) end @controller_name = @name.pluralize diff --git a/vendor/rails/railties/lib/tasks/databases.rake b/vendor/rails/railties/lib/tasks/databases.rake index 9588fabb..8b608396 100644 --- a/vendor/rails/railties/lib/tasks/databases.rake +++ b/vendor/rails/railties/lib/tasks/databases.rake @@ -55,7 +55,7 @@ namespace :db do case config['adapter'] when 'mysql' @charset = ENV['CHARSET'] || 'utf8' - @collation = ENV['COLLATION'] || 'utf8_general_ci' + @collation = ENV['COLLATION'] || 'utf8_unicode_ci' begin ActiveRecord::Base.establish_connection(config.merge('database' => nil)) ActiveRecord::Base.connection.create_database(config['database'], :charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)) @@ -156,8 +156,8 @@ namespace :db do Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby end - desc 'Drops and recreates the database from db/schema.rb for the current environment.' - task :reset => ['db:drop', 'db:create', 'db:schema:load'] + desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' + task :reset => [ 'db:drop', 'db:setup' ] desc "Retrieves the charset for the current environment's database" task :charset => :environment do @@ -206,6 +206,15 @@ namespace :db do end end + desc 'Create the database, load the schema, and initialize with the seed data' + task :setup => [ 'db:create', 'db:schema:load', 'db:seed' ] + + desc 'Load the seed data from db/seeds.rb' + task :seed => :environment do + seed_file = File.join(Rails.root, 'db', 'seeds.rb') + load(seed_file) if File.exist?(seed_file) + end + namespace :fixtures do desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." task :load => :environment do @@ -256,7 +265,11 @@ namespace :db do desc "Load a schema.rb file into the database" task :load => :environment do file = ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb" - load(file) + if File.exists?(file) + load(file) + else + abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{RAILS_ROOT}/config/environment.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]} + end end end diff --git a/vendor/rails/railties/lib/tasks/routes.rake b/vendor/rails/railties/lib/tasks/routes.rake index 39b71391..abbf3258 100644 --- a/vendor/rails/railties/lib/tasks/routes.rake +++ b/vendor/rails/railties/lib/tasks/routes.rake @@ -1,6 +1,7 @@ -desc 'Print out all defined routes in match order, with names.' +desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.' task :routes => :environment do - routes = ActionController::Routing::Routes.routes.collect do |route| + all_routes = ENV['CONTROLLER'] ? ActionController::Routing::Routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : ActionController::Routing::Routes.routes + routes = all_routes.collect do |route| name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s verb = route.conditions[:method].to_s.upcase segs = route.segments.inject("") { |str,s| str << s.to_s } @@ -14,4 +15,4 @@ task :routes => :environment do routes.each do |r| puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:segs].ljust(segs_width)} #{r[:reqs]}" end -end \ No newline at end of file +end diff --git a/vendor/rails/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml b/vendor/rails/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml new file mode 100644 index 00000000..641a7e03 --- /dev/null +++ b/vendor/rails/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml @@ -0,0 +1,2 @@ +en: + hello: "Hello from Engine" diff --git a/vendor/rails/railties/test/generators/generator_test_helper.rb b/vendor/rails/railties/test/generators/generator_test_helper.rb index 01bf1c90..6f02eb38 100644 --- a/vendor/rails/railties/test/generators/generator_test_helper.rb +++ b/vendor/rails/railties/test/generators/generator_test_helper.rb @@ -264,6 +264,8 @@ class GeneratorTestCase < Test::Unit::TestCase def assert_generated_migration(name, parent = "ActiveRecord::Migration") file = Dir.glob("#{RAILS_ROOT}/db/migrate/*_#{name.to_s.underscore}.rb").first + assert !file.nil?, "should have generated the migration file but didn't" + file = file.match(/db\/migrate\/[0-9]+_\w+/).to_s assert_generated_class file, parent do |body| assert_match /timestamps/, body, "should have timestamps defined" @@ -300,4 +302,9 @@ class GeneratorTestCase < Test::Unit::TestCase def assert_generated_column(body, name, type) assert_match /t\.#{type.to_s} :#{name.to_s}/, body, "should have column #{name.to_s} defined" end + + # Asserts that the given table is defined in the migration. + def assert_generated_table(body, name) + assert_match /create_table :#{name.to_s} do/, body, "should have table #{name.to_s} defined" + end end diff --git a/vendor/rails/railties/test/generators/rails_model_generator_test.rb b/vendor/rails/railties/test/generators/rails_model_generator_test.rb index aea2abaf..208b0c49 100644 --- a/vendor/rails/railties/test/generators/rails_model_generator_test.rb +++ b/vendor/rails/railties/test/generators/rails_model_generator_test.rb @@ -45,4 +45,52 @@ class RailsModelGeneratorTest < GeneratorTestCase assert body =~ /^\s+belongs_to :supplier/, "#{body.inspect} should contain 'belongs_to :supplier'" end end + + def test_migration_with_namespace + run_generator('model', %w(Gallery::Image)) + assert_generated_migration :gallery_images + assert_skipped_migration :create_images + end + + def test_migration_with_nested_namespace + run_generator('model', %w(Admin::Gallery::Image)) + assert_skipped_migration :create_images + assert_skipped_migration :create_gallery_images + + assert_generated_migration :admin_gallery_images do |t| + assert_generated_table t, :admin_gallery_images + end + end + + def test_migration_with_nested_namespace_without_pluralization + ActiveRecord::Base.pluralize_table_names = false + run_generator('model', %w(Admin::Gallery::Image)) + assert_skipped_migration :create_images + assert_skipped_migration :create_gallery_images + assert_skipped_migration :create_admin_gallery_images + assert_generated_migration :admin_gallery_image do |t| + assert_generated_table t, :admin_gallery_image + end + ensure + ActiveRecord::Base.pluralize_table_names = true + end + + def test_migration_with_namespaces_in_model_name_without_plurization + ActiveRecord::Base.pluralize_table_names = false + run_generator('model', %w(Gallery::Image)) + assert_generated_migration :create_gallery_image + assert_skipped_migration :create_gallery_images + ensure + ActiveRecord::Base.pluralize_table_names = true + end + + def test_migration_without_pluralization + ActiveRecord::Base.pluralize_table_names = false + run_generator('model', %w(Account)) + assert_generated_migration :create_account + assert_skipped_migration :create_accounts + ensure + ActiveRecord::Base.pluralize_table_names = true + end + end diff --git a/vendor/rails/railties/test/generators/rails_scaffold_generator_test.rb b/vendor/rails/railties/test/generators/rails_scaffold_generator_test.rb index 70829a77..2c60eb07 100644 --- a/vendor/rails/railties/test/generators/rails_scaffold_generator_test.rb +++ b/vendor/rails/railties/test/generators/rails_scaffold_generator_test.rb @@ -116,6 +116,7 @@ class RailsScaffoldGeneratorTest < GeneratorTestCase assert_equal "product_lines", g.controller_plural_name assert_equal "product_lines", g.controller_file_name assert_equal "product_lines", g.controller_table_name + assert_equal "ProductLine", g.class_name end def test_scaffold_plural_model_name_without_force_plural_generates_singular_model diff --git a/vendor/rails/railties/test/initializer_test.rb b/vendor/rails/railties/test/initializer_test.rb index ace0ccaf..68c99ee0 100644 --- a/vendor/rails/railties/test/initializer_test.rb +++ b/vendor/rails/railties/test/initializer_test.rb @@ -313,6 +313,8 @@ class InitializerSetupI18nTests < Test::Unit::TestCase File.expand_path(File.dirname(__FILE__) + "/../../activesupport/lib/active_support/locale/en.yml"), File.expand_path(File.dirname(__FILE__) + "/../../actionpack/lib/action_view/locale/en.yml"), File.expand_path(File.dirname(__FILE__) + "/../../activerecord/lib/active_record/locale/en.yml"), + # FIXME: should clean I18n.load_path between each test + File.expand_path(File.dirname(__FILE__) + "/../../railties/test/fixtures/plugins/engines/engine/config/locales/en.yml"), "my/test/locale.yml", "my/other/locale.yml" ], I18n.load_path.collect { |path| path =~ /^\./ ? File.expand_path(path) : path } end diff --git a/vendor/rails/railties/test/plugin_loader_test.rb b/vendor/rails/railties/test/plugin_loader_test.rb index b270748d..b0ab4b77 100644 --- a/vendor/rails/railties/test/plugin_loader_test.rb +++ b/vendor/rails/railties/test/plugin_loader_test.rb @@ -155,6 +155,14 @@ class TestPluginLoader < Test::Unit::TestCase plugin_load_paths.each { |path| assert $LOAD_PATH.include?(path) } end + def test_should_add_locale_files_to_I18n_load_path + only_load_the_following_plugins! [:engine] + + @loader.send :add_engine_locales + + assert I18n.load_path.include?(File.join(plugin_fixture_path('engines/engine'), 'config', 'locales', 'en.yml')) + end + private def reset_load_path!