diff options
107 files changed, 1314 insertions, 424 deletions
diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..d33c6a3c86 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +script: 'ci/travis.rb' +notifications: + disabled: true +rvm: + - 1.8.7 + - 1.9.2 +env: + - "GEM=railties" + - "GEM=ap,am,amo,ares,as" + - "GEM=ar:mysql" + - "GEM=ar:mysql2" + - "GEM=ar:sqlite3"
\ No newline at end of file @@ -31,13 +31,13 @@ gem "memcache-client", ">= 1.8.5" platforms :mri_18 do gem "system_timer" - gem "ruby-debug", ">= 0.10.3" + gem "ruby-debug", ">= 0.10.3" unless ENV['TRAVIS'] gem "json" end platforms :mri_19 do # TODO: Remove the conditional when ruby-debug19 supports Ruby >= 1.9.3 - gem "ruby-debug19", :require => "ruby-debug" if RUBY_VERSION < "1.9.3" + gem "ruby-debug19", :require => "ruby-debug" unless RUBY_VERSION > "1.9.2" || ENV['TRAVIS'] end platforms :ruby do @@ -56,7 +56,7 @@ platforms :ruby do gem "sqlite3", "~> 1.3.3" group :db do - gem "pg", ">= 0.11.0" + gem "pg", ">= 0.11.0" unless ENV['TRAVIS'] # once pg is on travis this can be removed gem "mysql", ">= 2.8.1" gem "mysql2", ">= 0.3.6" end diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index b0f7d0bc11..9888be07a9 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -30,6 +30,8 @@ *Rails 3.1.0 (unreleased)* +* The submit form helper does not generate an id "object_name_id" anymore. [fbrusatti] + * Make sure respond_with with :js tries to render a template in all cases [José Valim] * json_escape will now return a SafeBuffer string if it receives SafeBuffer string [tenderlove] @@ -101,11 +103,11 @@ For example if you have this route: - map '*pages' => 'pages#show' + match '*pages' => 'pages#show' by requesting '/foo/bar.json', your `params[:pages]` will be equals to "foo/bar" with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `:format => false` like this: - map '*pages' => 'pages#show', :format => false + match '*pages' => 'pages#show', :format => false * Added Base.http_basic_authenticate_with to do simple http basic authentication with a single class method call [DHH] diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 642fbcb8e6..15d104fd82 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,10 +21,11 @@ Gem::Specification.new do |s| s.add_dependency('rack-cache', '~> 1.0.2') s.add_dependency('builder', '~> 3.0.0') s.add_dependency('i18n', '~> 0.6') - s.add_dependency('rack', '~> 1.3.0') + s.add_dependency('rack', '~> 1.3.1') s.add_dependency('rack-test', '~> 0.6.0') s.add_dependency('rack-mount', '~> 0.8.1') s.add_dependency('sprockets', '= 2.0.0.beta.10') - s.add_dependency('tzinfo', '~> 0.3.29') s.add_dependency('erubis', '~> 2.7.0') + + s.add_development_dependency('tzinfo', '~> 0.3.29') end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index d6f75bded0..10aa34c76b 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -139,7 +139,7 @@ module AbstractController # # end # - # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will + # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will # be rendered directly, without wrapping a layout around the rendered view. # # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so @@ -167,6 +167,7 @@ module AbstractController included do class_attribute :_layout_conditions + remove_possible_method :_layout_conditions delegate :_layout_conditions, :to => :'self.class' self._layout_conditions = {} _write_layout_method diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index f13fd71050..f4eaa2fd1b 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -47,21 +47,6 @@ module ActionController eager_autoload do autoload :RecordIdentifier - - # TODO: Don't autoload exceptions, setup explicit - # requires for files that need them - autoload_at "action_controller/metal/exceptions" do - autoload :ActionControllerError - autoload :RenderError - autoload :RoutingError - autoload :MethodNotAllowed - autoload :NotImplemented - autoload :UnknownController - autoload :MissingFile - autoload :RenderError - autoload :SessionOverflowError - autoload :UnknownHttpMethod - end end end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index d14c5f940b..ce56d8bc71 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -212,16 +212,16 @@ module ActionController # also include them at the bottom. AbstractController::Callbacks, + # Append rescue at the bottom to wrap as much as possible. + Rescue, + # Add instrumentations hooks at the bottom, to ensure they instrument # all the methods properly. Instrumentation, # Params wrapper should come before instrumentation so they are # properly showed in logs - ParamsWrapper, - - # The same with rescue, append it at the end to wrap as much as possible. - Rescue + ParamsWrapper ] MODULES.each do |mod| diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index 50827d8107..0785fe9679 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/file/path' +require 'action_controller/metal/exceptions' module ActionController #:nodoc: # Methods for sending arbitrary data and for streaming files to the browser, diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 2271470334..258a40aea6 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/class/attribute' +require 'action_controller/metal/exceptions' module ActionController #:nodoc: class InvalidAuthenticityToken < ActionControllerError #:nodoc: diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 980c658ab7..5c48a60469 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -98,7 +98,8 @@ module ActionDispatch BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ def valid_accept_header - xhr? || (accept && accept !~ BROWSER_LIKE_ACCEPTS) + (xhr? && (accept || content_mime_type)) || + (accept && accept !~ BROWSER_LIKE_ACCEPTS) end def use_accept_header diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index b22d426c1f..b80574f497 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -6,6 +6,7 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/string/access' require 'active_support/inflector' require 'action_dispatch/http/headers' +require 'action_controller/metal/exceptions' module ActionDispatch class Request < Rack::Request diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index c17c746096..a765c23dae 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/exception' +require 'action_controller/metal/exceptions' require 'active_support/notifications' require 'action_dispatch/http/request' diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 65895590bf..8d071b2061 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -659,13 +659,13 @@ module ActionDispatch # # This generates the following routes: # - # admin_posts GET /admin/posts(.:format) {:action=>"index", :controller=>"admin/posts"} - # admin_posts POST /admin/posts(.:format) {:action=>"create", :controller=>"admin/posts"} - # new_admin_post GET /admin/posts/new(.:format) {:action=>"new", :controller=>"admin/posts"} - # edit_admin_post GET /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"} - # admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"} - # admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"} - # admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"} + # admin_posts GET /admin/posts(.:format) admin/posts#index + # admin_posts POST /admin/posts(.:format) admin/posts#create + # new_admin_post GET /admin/posts/new(.:format) admin/posts#new + # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit + # admin_post GET /admin/posts/:id(.:format) admin/posts#show + # admin_post PUT /admin/posts/:id(.:format) admin/posts#update + # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy # # === Options # diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 5097f6732d..11228c597d 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/module/remove_method' +require 'action_controller/metal/exceptions' module ActionDispatch module Routing diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 57a24a1bd9..b10aab9029 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -1,6 +1,7 @@ require 'uri' require 'active_support/core_ext/hash/diff' require 'active_support/core_ext/hash/indifferent_access' +require 'action_controller/metal/exceptions' module ActionDispatch module Assertions diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb index 96d8fd0dfe..aae8377f8a 100644 --- a/actionpack/lib/action_view/asset_paths.rb +++ b/actionpack/lib/action_view/asset_paths.rb @@ -1,5 +1,6 @@ require 'zlib' require 'active_support/core_ext/file' +require 'action_controller/metal/exceptions' module ActionView class AssetPaths #:nodoc: diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 0c3f011c92..509c29844a 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -1,6 +1,7 @@ require 'action_view/helpers/asset_tag_helpers/javascript_tag_helpers' require 'action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers' require 'action_view/helpers/asset_tag_helpers/asset_paths' +require 'action_view/helpers/tag_helper' module ActionView # = Action View Asset Tag Helpers @@ -191,6 +192,7 @@ module ActionView # RewriteEngine On # RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L] module AssetTagHelper + include TagHelper include JavascriptTagHelpers include StylesheetTagHelpers # Returns a link tag that browsers and news readers can use to auto-detect diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 3dc6d65432..7c43dc04e0 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -105,7 +105,10 @@ module ActionView # Create a select tag and a series of contained option tags for the provided object and method. # The option currently held by the object will be selected, provided that the object is available. - # See options_for_select for the required format of the choices parameter. + # + # There are two possible formats for the choices parameter, corresponding to other helpers' output: + # * A flat collection: see options_for_select + # * A nested collection: see grouped_options_for_select # # Example with @post.person_id => 1: # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true }) @@ -575,7 +578,14 @@ module ActionView def to_select_tag(choices, options, html_options) selected_value = options.has_key?(:selected) ? options[:selected] : value(object) - select_content_tag(options_for_select(choices, :selected => selected_value, :disabled => options[:disabled]), options, html_options) + + if !choices.empty? && choices.try(:first).try(:second).respond_to?(:each) + option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled]) + else + option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled]) + end + + select_content_tag(option_tags, options, html_options) end def to_collection_select_tag(collection, value_method, text_method, options, html_options) diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index c6098fc7e0..51c784493e 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -291,6 +291,11 @@ module ActionView else paths.map! { |path| retrieve_variable(path).unshift(path) } end + if String === partial && @variable.to_s !~ /^[a-z_][a-zA-Z_0-9]*$/ + raise ArgumentError.new("The partial name (#{partial}) is not a valid Ruby identifier; " + + "make sure your partial name starts with a letter or underscore, " + + "and is followed by any combinations of letters, numbers, or underscores.") + end self end diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake index 0236350576..a68f0e84f8 100644 --- a/actionpack/lib/sprockets/assets.rake +++ b/actionpack/lib/sprockets/assets.rake @@ -1,26 +1,22 @@ namespace :assets do + # Ensures the RAILS_GROUPS environment variable is set + task :ensure_env do + ENV["RAILS_GROUPS"] ||= "assets" + end + desc "Compile all the assets named in config.assets.precompile" - task :precompile do - if ENV["RAILS_GROUPS"].to_s.empty? - ENV["RAILS_GROUPS"] = "assets" - Kernel.exec $0, *ARGV - else - Rake::Task["environment"].invoke - Sprockets::Helpers::RailsHelper + task :precompile => :ensure_env do + Rake::Task["environment"].invoke + Sprockets::Helpers::RailsHelper - assets = Rails.application.config.assets.precompile - Rails.application.assets.precompile(*assets) - end + assets = Rails.application.config.assets.precompile + Rails.application.assets.precompile(*assets) end desc "Remove compiled assets" task :clean => :environment do assets = Rails.application.config.assets public_asset_path = Rails.public_path + assets.prefix - file_list = FileList.new("#{public_asset_path}/**/*") - file_list.each do |file| - rm_rf file - rm_rf "#{file}.gz" - end + rm_rf public_asset_path, :secure => true end end diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index c28bdc3061..4906ad9a9c 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -63,15 +63,20 @@ module Sprockets env.logger = Rails.logger - if env.respond_to?(:cache) - env.cache = assets.cache_store || Rails.cache + if env.respond_to?(:cache) && assets.cache_store != false + env.cache = ActiveSupport::Cache.lookup_store(assets.cache_store) || Rails.cache end if assets.compress # temporarily hardcode default JS compressor to uglify. Soon, it will work # the same as SCSS, where a default plugin sets the default. - env.js_compressor = LazyCompressor.new { expand_js_compressor(assets.js_compressor || :uglifier) } - env.css_compressor = LazyCompressor.new { expand_css_compressor(assets.css_compressor) } + unless assets.js_compressor == false + env.js_compressor = LazyCompressor.new { expand_js_compressor(assets.js_compressor || :uglifier) } + end + + unless assets.css_compressor == false + env.css_compressor = LazyCompressor.new { expand_css_compressor(assets.css_compressor) } + end end env diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb index f0fb113860..768ac713ca 100644 --- a/actionpack/test/activerecord/active_record_store_test.rb +++ b/actionpack/test/activerecord/active_record_store_test.rb @@ -225,6 +225,36 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest assert_equal session_id, cookies['_session_id'] end end + + def test_incoming_invalid_session_id_via_cookie_should_be_ignored + with_test_route_set do + open_session do |sess| + sess.cookies['_session_id'] = 'INVALID' + + sess.get '/set_session_value' + new_session_id = sess.cookies['_session_id'] + assert_not_equal 'INVALID', new_session_id + + sess.get '/get_session_value' + new_session_id_2 = sess.cookies['_session_id'] + assert_equal new_session_id, new_session_id_2 + end + end + end + + def test_incoming_invalid_session_id_via_parameter_should_be_ignored + with_test_route_set(:cookie_only => false) do + open_session do |sess| + sess.get '/set_session_value', :_session_id => 'INVALID' + new_session_id = sess.cookies['_session_id'] + assert_not_equal 'INVALID', new_session_id + + sess.get '/get_session_value' + new_session_id_2 = sess.cookies['_session_id'] + assert_equal new_session_id, new_session_id_2 + end + end + end private @@ -247,6 +277,7 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest session_class, ActiveRecord::SessionStore.session_class = ActiveRecord::SessionStore.session_class, "ActiveRecord::SessionStore::#{class_name.camelize}".constantize yield + ensure ActiveRecord::SessionStore.session_class = session_class end end diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index 80c4fa2ee5..ccdfcb0b2c 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -6,6 +6,13 @@ module Another class LogSubscribersController < ActionController::Base wrap_parameters :person, :include => :name, :format => :json + class SpecialException < Exception + end + + rescue_from SpecialException do + head :status => 406 + end + def show render :nothing => true end @@ -39,6 +46,10 @@ module Another raise Exception end + def with_rescued_exception + raise SpecialException + end + end end @@ -195,6 +206,14 @@ class ACLogSubscriberTest < ActionController::TestCase assert_match(/Completed 500/, logs.last) end + def test_process_action_with_rescued_exception_includes_http_status_code + get :with_rescued_exception + wait + + assert_equal 2, logs.size + assert_match(/Completed 406/, logs.last) + end + def logs @logs ||= @logger.logged(:info) end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 25b1b4f745..060bcfb5ec 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -468,6 +468,12 @@ class RequestTest < ActiveSupport::TestCase assert request.formats.empty? end + test "formats with xhr request" do + request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + request.expects(:parameters).at_least_once.returns({}) + assert_equal [Mime::JS], request.formats + end + test "ignore_accept_header" do ActionDispatch::Request.ignore_accept_header = true diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index aa115608ef..4ee1d61146 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -40,6 +40,11 @@ class TestRequestTest < ActiveSupport::TestCase req.cookie_jar["login"] = "XJ-122" assert_cookies({"user_name" => "david", "login" => "XJ-122"}, req.cookie_jar) + assert_nothing_raised do + req.cookie_jar["login"] = nil + assert_cookies({"user_name" => "david", "login" => nil}, req.cookie_jar) + end + req.cookie_jar.delete(:login) assert_cookies({"user_name" => "david"}, req.cookie_jar) diff --git a/actionpack/test/fixtures/test/_200.html.erb b/actionpack/test/fixtures/test/_200.html.erb new file mode 100644 index 0000000000..c9f45675dc --- /dev/null +++ b/actionpack/test/fixtures/test/_200.html.erb @@ -0,0 +1 @@ +<h1>Invalid partial</h1> diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index a4599a3f00..6aea991f7c 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -385,6 +385,42 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_with_grouped_collection_as_nested_array + @post = Post.new + + countries_by_continent = [ + ["<Africa>", [["<South Africa>", "<sa>"], ["Somalia", "so"]]], + ["Europe", [["Denmark", "dk"], ["Ireland", "ie"]]], + ] + + assert_dom_equal( + [ + %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>}, + %Q{<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>}, + %Q{<option value="ie">Ireland</option></optgroup></select>}, + ].join("\n"), + select("post", "origin", countries_by_continent) + ) + end + + def test_select_with_grouped_collection_as_hash + @post = Post.new + + countries_by_continent = { + "<Africa>" => [["<South Africa>", "<sa>"], ["Somalia", "so"]], + "Europe" => [["Denmark", "dk"], ["Ireland", "ie"]], + } + + assert_dom_equal( + [ + %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>}, + %Q{<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>}, + %Q{<option value="ie">Ireland</option></optgroup></select>}, + ].join("\n"), + select("post", "origin", countries_by_continent) + ) + end + def test_select_with_boolean_method @post = Post.new @post.allow_comments = false diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 4187a0ac78..68b2ed45d1 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -98,6 +98,15 @@ module RenderTestCases assert_equal "only partial", @view.render("test/partial_only", :counter_counter => 5) end + def test_render_partial_with_invalid_name + @view.render(:partial => "test/200") + flunk "Render did not raise ArgumentError" + rescue ArgumentError => e + assert_equal "The partial name (test/200) is not a valid Ruby identifier; " + + "make sure your partial name starts with a letter or underscore, " + + "and is followed by any combinations of letters, numbers, or underscores.", e.message + end + def test_render_partial_with_errors @view.render(:partial => "test/raise") flunk "Render did not raise Template::Error" diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index a70c02a429..78245c1f95 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -386,13 +386,11 @@ class UrlHelperTest < ActiveSupport::TestCase def test_mail_to_with_javascript snippet = mail_to("me@domain.com", "My email", :encode => "javascript") assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet - assert snippet.html_safe? end def test_mail_to_with_javascript_unicode snippet = mail_to("unicode@example.com", "únicode", :encode => "javascript") assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet - assert snippet.html_safe end def test_mail_with_options @@ -421,6 +419,12 @@ class UrlHelperTest < ActiveSupport::TestCase assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") end + def test_mail_to_returns_html_safe_string + assert mail_to("david@loudthinking.com").html_safe? + assert mail_to("me@domain.com", "My email", :encode => "javascript").html_safe? + assert mail_to("me@domain.com", "My email", :encode => "hex").html_safe? + end + # TODO: button_to looks at this ... why? def protect_against_forgery? false diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 6ee5e04267..bdc0eb4a0d 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -314,6 +314,7 @@ module ActiveModel end end end + attribute_method_matchers_cache.clear end # Removes all the previously dynamically defined methods from the class @@ -321,6 +322,7 @@ module ActiveModel generated_attribute_methods.module_eval do instance_methods.each { |m| undef_method(m) } end + attribute_method_matchers_cache.clear end # Returns true if the attribute methods defined have been generated. @@ -338,6 +340,29 @@ module ActiveModel end private + # The methods +method_missing+ and +respond_to?+ of this module are + # invoked often in a typical rails, both of which invoke the method + # +match_attribute_method?+. The latter method iterates through an + # array doing regular expression matches, which results in a lot of + # object creations. Most of the times it returns a +nil+ match. As the + # match result is always the same given a +method_name+, this cache is + # used to alleviate the GC, which ultimately also speeds up the app + # significantly (in our case our test suite finishes 10% faster with + # this cache). + def attribute_method_matchers_cache + @attribute_method_matchers_cache ||= {} + end + + def attribute_method_matcher(method_name) + if attribute_method_matchers_cache.key?(method_name) + attribute_method_matchers_cache[method_name] + else + match = nil + attribute_method_matchers.detect { |method| match = method.match(method_name) } + attribute_method_matchers_cache[method_name] = match + end + end + class AttributeMethodMatcher attr_reader :prefix, :suffix, :method_missing_target @@ -411,12 +436,8 @@ module ActiveModel # Returns a struct representing the matching attribute method. # The struct's attributes are prefix, base and suffix. def match_attribute_method?(method_name) - self.class.attribute_method_matchers.each do |method| - if (match = method.match(method_name)) && attribute_method?(match.attr_name) - return match - end - end - nil + match = self.class.send(:attribute_method_matcher, method_name) + match && attribute_method?(match.attr_name) ? match : nil end # prevent method_missing from calling private methods with #send diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 4a174cb62a..9260c5082d 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -69,18 +69,46 @@ module ActiveModel def serializable_hash(options = nil) options ||= {} - only = Array.wrap(options[:only]).map(&:to_s) - except = Array.wrap(options[:except]).map(&:to_s) - attribute_names = attributes.keys.sort - if only.any? - attribute_names &= only - elsif except.any? - attribute_names -= except + if only = options[:only] + attribute_names &= Array.wrap(only).map(&:to_s) + elsif except = options[:except] + attribute_names -= Array.wrap(except).map(&:to_s) + end + + method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) } + hash = Hash[(attribute_names + method_names).map { |n| [n, send(n)] }] + + serializable_add_includes(options) do |association, records, opts| + hash[association] = if records.is_a?(Enumerable) + records.map { |a| a.serializable_hash(opts) } + else + records.serializable_hash(opts) + end end - method_names = Array.wrap(options[:methods]).map { |n| n if respond_to?(n.to_s) }.compact - Hash[(attribute_names + method_names).map { |n| [n, send(n)] }] + hash end + + private + # Add associations specified via the <tt>:include</tt> option. + # + # Expects a block that takes as arguments: + # +association+ - name of the association + # +records+ - the association record(s) to be serialized + # +opts+ - options for the association records + def serializable_add_includes(options = {}) + return unless include = options[:include] + + unless include.is_a?(Hash) + include = Hash[Array.wrap(include).map { |n| [n, {}] }] + end + + include.each do |association, opts| + if records = send(association) + yield association, records, opts + end + end + end end end diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 9812af43d6..64dda3bcee 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -101,6 +101,7 @@ module ActiveModel @builder.tag!(*args) do add_attributes_and_methods + add_includes add_extra_behavior add_procs yield @builder if block_given? @@ -120,6 +121,45 @@ module ActiveModel end end + def add_includes + @serializable.send(:serializable_add_includes, options) do |association, records, opts| + add_associations(association, records, opts) + end + end + + # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well. + def add_associations(association, records, opts) + merged_options = opts.merge(options.slice(:builder, :indent)) + merged_options[:skip_instruct] = true + + if records.is_a?(Enumerable) + tag = ActiveSupport::XmlMini.rename_key(association.to_s, options) + type = options[:skip_types] ? { } : {:type => "array"} + association_name = association.to_s.singularize + merged_options[:root] = association_name + + if records.empty? + @builder.tag!(tag, type) + else + @builder.tag!(tag, type) do + records.each do |record| + if options[:skip_types] + record_type = {} + else + record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name + record_type = {:type => record_class} + end + + record.to_xml merged_options.merge(record_type) + end + end + end + else + merged_options[:root] = association.to_s + records.to_xml(merged_options) + end + end + def add_procs if procs = options.delete(:procs) Array.wrap(procs).each do |proc| diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb index 8cc1ccb1e7..5122f08eec 100644 --- a/activemodel/test/cases/serialization_test.rb +++ b/activemodel/test/cases/serialization_test.rb @@ -1,13 +1,19 @@ require "cases/helper" +require 'active_support/core_ext/object/instance_variables' class SerializationTest < ActiveModel::TestCase class User include ActiveModel::Serialization - attr_accessor :name, :email, :gender + attr_accessor :name, :email, :gender, :address, :friends + + def initialize(name, email, gender) + @name, @email, @gender = name, email, gender + @friends = [] + end def attributes - @attributes ||= {'name' => 'nil', 'email' => 'nil', 'gender' => 'nil'} + instance_values.except("address", "friends") end def foo @@ -15,11 +21,25 @@ class SerializationTest < ActiveModel::TestCase end end + class Address + include ActiveModel::Serialization + + attr_accessor :street, :city, :state, :zip + + def attributes + instance_values + end + end + setup do - @user = User.new - @user.name = 'David' - @user.email = 'david@example.com' - @user.gender = 'male' + @user = User.new('David', 'david@example.com', 'male') + @user.address = Address.new + @user.address.street = "123 Lane" + @user.address.city = "Springfield" + @user.address.state = "CA" + @user.address.zip = 11111 + @user.friends = [User.new('Joe', 'joe@example.com', 'male'), + User.new('Sue', 'sue@example.com', 'female')] end def test_method_serializable_hash_should_work @@ -42,4 +62,60 @@ class SerializationTest < ActiveModel::TestCase assert_equal expected , @user.serializable_hash(:methods => [:foo]) end + def test_method_serializable_hash_should_work_with_only_and_methods + expected = {:foo=>"i_am_foo"} + assert_equal expected , @user.serializable_hash(:only => [], :methods => [:foo]) + end + + def test_method_serializable_hash_should_work_with_except_and_methods + expected = {"gender"=>"male", :foo=>"i_am_foo"} + assert_equal expected , @user.serializable_hash(:except => [:name, :email], :methods => [:foo]) + end + + def test_should_not_call_methods_that_dont_respond + expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} + assert_equal expected , @user.serializable_hash(:methods => [:bar]) + end + + def test_include_option_with_singular_association + expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", + :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} + assert_equal expected , @user.serializable_hash(:include => :address) + end + + def test_include_option_with_plural_association + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + assert_equal expected , @user.serializable_hash(:include => :friends) + end + + def test_include_option_with_empty_association + @user.friends = [] + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]} + assert_equal expected , @user.serializable_hash(:include => :friends) + end + + def test_multiple_includes + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, + :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + assert_equal expected , @user.serializable_hash(:include => [:address, :friends]) + end + + def test_include_with_options + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :address=>{"street"=>"123 Lane"}} + assert_equal expected , @user.serializable_hash(:include => {:address => {:only => "street"}}) + end + + def test_nested_include + @user.friends.first.friends = [@user] + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', + :friends => ["email"=>"david@example.com", "gender"=>"male", "name"=>"David"]}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', :friends => []}]} + assert_equal expected , @user.serializable_hash(:include => {:friends => {:include => :friends}}) + end end diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb index f978191d22..a38ef8e223 100644 --- a/activemodel/test/cases/serializers/xml_serialization_test.rb +++ b/activemodel/test/cases/serializers/xml_serialization_test.rb @@ -7,9 +7,11 @@ class Contact extend ActiveModel::Naming include ActiveModel::Serializers::Xml + attr_accessor :address, :friends + def attributes - instance_values - end unless method_defined?(:attributes) + instance_values.except("address", "friends") + end end module Admin @@ -20,6 +22,17 @@ end class Customer < Struct.new(:name) end +class Address + extend ActiveModel::Naming + include ActiveModel::Serializers::Xml + + attr_accessor :street, :city, :state, :zip + + def attributes + instance_values + end +end + class XmlSerializationTest < ActiveModel::TestCase def setup @contact = Contact.new @@ -30,6 +43,12 @@ class XmlSerializationTest < ActiveModel::TestCase customer = Customer.new customer.name = "John" @contact.preferences = customer + @contact.address = Address.new + @contact.address.street = "123 Lane" + @contact.address.city = "Springfield" + @contact.address.state = "CA" + @contact.address.zip = 11111 + @contact.friends = [Contact.new, Contact.new] end test "should serialize default root" do @@ -138,4 +157,33 @@ class XmlSerializationTest < ActiveModel::TestCase assert_match %r{<contact type="Contact">}, xml assert_match %r{<name>aaron stack</name>}, xml end + + test "include option with singular association" do + xml = @contact.to_xml :include => :address, :indent => 0 + assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true)) + end + + test "include option with plural association" do + xml = @contact.to_xml :include => :friends, :indent => 0 + assert_match %r{<friends type="array">}, xml + assert_match %r{<friend type="Contact">}, xml + end + + test "multiple includes" do + xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => [ :address, :friends ] + assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true)) + assert_match %r{<friends type="array">}, xml + assert_match %r{<friend type="Contact">}, xml + end + + test "include with options" do + xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => { :address => { :only => :city } } + assert xml.include?(%(><address><city>Springfield</city></address>)) + end + + test "propagates skip_types option to included associations" do + xml = @contact.to_xml :include => :friends, :indent => 0, :skip_types => true + assert_match %r{<friends>}, xml + assert_match %r{<friend>}, xml + end end diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 4113a16c12..d24ca2716e 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,3 +1,22 @@ +*Rails 3.2.0 (unreleased)* + +* ActiveRecord::Base.establish_connection now takes a string that contains +a URI that specifies the connection configuration. For example: + + ActiveRecord::Base.establish_connection 'postgres://localhost/foo' + +* Active Record's dynamic finder will now raise the error if you passing in less number of arguments than what you call in method signature. + + So if you were doing this and expecting the second argument to be nil: + + User.find_by_username_and_group("sikachu") + + You'll now get `ArgumentError: wrong number of arguments (1 for 2).` You'll then have to do this: + + User.find_by_username_and_group("sikachu", nil) + + [Prem Sichanugrist] + *Rails 3.1.0 (unreleased)* * ActiveRecord::MacroReflection::AssociationReflection#build_record has a new method signature. diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index c263edd2c6..adb6af7165 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -37,7 +37,7 @@ module ActiveRecord # Checks whether record is different to the current target, without loading it def different_target?(record) record.nil? && owner[reflection.foreign_key] || - record.id != owner[reflection.foreign_key] + record && record.id != owner[reflection.foreign_key] end def replace_keys(record) diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index e3f221c773..8bd898d126 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -7,7 +7,7 @@ module ActiveRecord # the record is not persisted? or has just been destroyed. def to_key key = send(self.class.primary_key) - [key] if key + persisted? && key ? [key] : nil end module ClassMethods diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index daa1b2fcd6..99ed273a8a 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1052,20 +1052,15 @@ module ActiveRecord #:nodoc: # Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it # is first invoked, so that future attempts to use it do not run through method_missing. def method_missing(method_id, *arguments, &block) - if match = DynamicFinderMatch.match(method_id) + if match = (DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id)) attribute_names = match.attribute_names super unless all_attributes_exists?(attribute_names) - if match.finder? - options = arguments.extract_options! - relation = options.any? ? scoped(options) : scoped - relation.send :find_by_attributes, match, attribute_names, *arguments, &block - elsif match.instantiator? - scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block + if arguments.size < attribute_names.size + method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'" + backtrace = [method_trace] + caller + raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace end - elsif match = DynamicScopeMatch.match(method_id) - attribute_names = match.attribute_names - super unless all_attributes_exists?(attribute_names) - if match.scope? + if match.respond_to?(:scope?) && match.scope? self.class_eval <<-METHOD, __FILE__, __LINE__ + 1 def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args) attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)] @@ -1074,6 +1069,12 @@ module ActiveRecord #:nodoc: end # end METHOD send(method_id, *arguments) + elsif match.finder? + options = arguments.extract_options! + relation = options.any? ? scoped(options) : scoped + relation.send :find_by_attributes, match, attribute_names, *arguments, &block + elsif match.instantiator? + scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block end else super @@ -1638,7 +1639,8 @@ MSG when new_record? "#{self.class.model_name.cache_key}/new" when timestamp = self[:updated_at] - "#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}" + timestamp = timestamp.utc.to_s(:number) + "#{self.class.model_name.cache_key}/#{id}-#{timestamp}" else "#{self.class.model_name.cache_key}/#{id}" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index bcd3abc08d..123b3654e6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -46,6 +46,12 @@ module ActiveRecord # "database" => "path/to/dbfile" # ) # + # Or a URL: + # + # ActiveRecord::Base.establish_connection( + # "postgres://myuser:mypass@localhost/somedatabase" + # ) + # # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError # may be returned on an error. def self.establish_connection(spec = nil) @@ -58,6 +64,8 @@ module ActiveRecord when Symbol, String if configuration = configurations[spec.to_s] establish_connection(configuration) + elsif spec.is_a?(String) && hash = connection_url_to_hash(spec) + establish_connection(hash) else raise AdapterNotSpecified, "#{spec} database is not configured" end @@ -81,6 +89,24 @@ module ActiveRecord end end + def self.connection_url_to_hash(url) # :nodoc: + config = URI.parse url + adapter = config.scheme + adapter = "postgresql" if adapter == "postgres" + spec = { :adapter => adapter, + :username => config.user, + :password => config.password, + :port => config.port, + :database => config.path.sub(%r{^/},""), + :host => config.host } + spec.reject!{ |_,value| !value } + if config.query + options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys + spec.merge!(options) + end + spec + end + class << self # Returns the connection currently associated with the class. This can # also be used to "borrow" the connection to do database work unrelated diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 724b2e6d9c..e2a0f63393 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -157,6 +157,11 @@ module ActiveRecord end end + def type_cast(value, column) # :nodoc: + return super unless BigDecimal === value + + value.to_f + end # DATABASE STATEMENTS ====================================== diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index c459846264..507f345ef5 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/class/attribute_accessors" require "active_support/core_ext/array/wrap" module ActiveRecord diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 53617059d0..2dbebfcaf8 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -383,7 +383,7 @@ module ActiveRecord attributes_collection = if keys.include?('id') || keys.include?(:id) Array.wrap(attributes_collection) else - attributes_collection.sort_by { |i, _| i.to_i }.map { |_, attributes| attributes } + attributes_collection.values end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 6258a16d0f..2d0861d5c9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -408,7 +408,17 @@ module ActiveRecord end def eager_loading? - @should_eager_load ||= (@eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?)) + @should_eager_load ||= + @eager_load_values.any? || + @includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?) + end + + # Joins that are also marked for preloading. In which case we should just eager load them. + # Note that this is a naive implementation because we could have strings and symbols which + # represent the same association, but that aren't matched by this. Also, we could have + # nested hashes which partially match, e.g. { :a => :b } & { :a => [:b, :c] } + def joined_includes_values + @includes_values & @joins_values end def ==(other) diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 46ab67d1cf..ec1176e3dd 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -66,11 +66,14 @@ module ActiveRecord records = relation.where(table[primary_key].gteq(start)).all while records.any? + records_size = records.size + primary_key_offset = records.last.id + yield records - break if records.size < batch_size + break if records_size < batch_size - if primary_key_offset = records.last.id + if primary_key_offset records = relation.where(table[primary_key].gt(primary_key_offset)).to_a else raise "Primary key not included in the custom select clause" diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 422d89060b..8cef4e5554 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -193,8 +193,8 @@ module ActiveRecord else relation = relation.where(table[primary_key].eq(id)) if id end - - connection.select_value(relation.to_sql) ? true : false + + connection.select_value(relation.to_sql, "#{name} Exists") ? true : false end protected @@ -243,7 +243,7 @@ module ActiveRecord end def construct_limited_ids_condition(relation) - orders = relation.order_values + orders = relation.order_values.map { |val| val.presence }.compact values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders) relation = relation.dup diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index d17861f407..8bd4732c0c 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -96,11 +96,11 @@ module ActiveRecord relation end - def having(*args) - return self if args.blank? + def having(opts, *rest) + return self if opts.blank? relation = clone - relation.having_values += build_where(*args) + relation.having_values += build_where(opts, rest) relation end diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index e3185a9c5a..5ad40d8cd9 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -10,46 +10,8 @@ module ActiveRecord #:nodoc: options[:except] = Array.wrap(options[:except]).map { |n| n.to_s } options[:except] |= Array.wrap(self.class.inheritance_column) - hash = super(options) - - serializable_add_includes(options) do |association, records, opts| - hash[association] = records.is_a?(Enumerable) ? - records.map { |r| r.serializable_hash(opts) } : - records.serializable_hash(opts) - end - - hash + super(options) end - - private - # Add associations specified via the <tt>:include</tt> option. - # - # Expects a block that takes as arguments: - # +association+ - name of the association - # +records+ - the association record(s) to be serialized - # +opts+ - options for the association records - def serializable_add_includes(options = {}) - return unless include_associations = options.delete(:include) - - include_has_options = include_associations.is_a?(Hash) - associations = include_has_options ? include_associations.keys : Array.wrap(include_associations) - - associations.each do |association| - records = case self.class.reflect_on_association(association).macro - when :has_many, :has_and_belongs_to_many - send(association).to_a - when :has_one, :belongs_to - send(association) - end - - if records - association_options = include_has_options ? include_associations[association] : {} - yield(association, records, association_options) - end - end - - options[:include] = include_associations - end end end diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index f8e6cf958c..cbfa1ad609 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -182,48 +182,6 @@ module ActiveRecord #:nodoc: options[:except] |= Array.wrap(@serializable.class.inheritance_column) end - def add_extra_behavior - add_includes - end - - def add_includes - procs = options.delete(:procs) - @serializable.send(:serializable_add_includes, options) do |association, records, opts| - add_associations(association, records, opts) - end - options[:procs] = procs - end - - # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well. - def add_associations(association, records, opts) - association_name = association.to_s.singularize - merged_options = options.merge(opts).merge!(:root => association_name, :skip_instruct => true) - - if records.is_a?(Enumerable) - tag = ActiveSupport::XmlMini.rename_key(association.to_s, options) - type = options[:skip_types] ? { } : {:type => "array"} - - if records.empty? - @builder.tag!(tag, type) - else - @builder.tag!(tag, type) do - records.each do |record| - if options[:skip_types] - record_type = {} - else - record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name - record_type = {:type => record_class} - end - - record.to_xml merged_options.merge(record_type) - end - end - end - elsif record = @serializable.send(association) - record.to_xml(merged_options) - end - end - class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc: def compute_type klass = @serializable.class diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index 929559c3ba..76c37cc367 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -183,11 +183,6 @@ module ActiveRecord ## # :singleton-method: - # Use the ActiveRecord::Base.connection by default. - cattr_accessor :connection - - ## - # :singleton-method: # The table name defaults to 'sessions'. cattr_accessor :table_name @@table_name = 'sessions' @@ -206,10 +201,19 @@ module ActiveRecord class << self alias :data_column_name :data_column + + # Use the ActiveRecord::Base.connection by default. + attr_writer :connection + + # Use the ActiveRecord::Base.connection_pool by default. + attr_writer :connection_pool - remove_method :connection def connection - @@connection ||= ActiveRecord::Base.connection + @connection ||= ActiveRecord::Base.connection + end + + def connection_pool + @connection_pool ||= ActiveRecord::Base.connection_pool end # Look up a session by id and unmarshal its data if found. @@ -219,6 +223,8 @@ module ActiveRecord end end end + + delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self attr_reader :session_id, :new_record alias :new_record? :new_record @@ -297,8 +303,12 @@ module ActiveRecord private def get_session(env, sid) Base.silence do - sid ||= generate_sid - session = find_session(sid) + unless sid and session = @@session_class.find_by_session_id(sid) + # If the sid was nil or if there is no pre-existing session under the sid, + # force the generation of a new sid and associate a new session associated with the new sid + sid = generate_sid + session = @@session_class.new(:session_id => sid, :data => {}) + end env[SESSION_RECORD_KEY] = session [sid, session.data] end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 07de544868..3942e7bb41 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -81,12 +81,6 @@ class AdapterTest < ActiveRecord::TestCase end end - if current_adapter?(:PostgreSQLAdapter) - def test_encoding - assert_not_nil @connection.encoding - end - end - def test_table_alias def @connection.test_table_alias_length() 10; end class << @connection @@ -146,4 +140,18 @@ class AdapterTest < ActiveRecord::TestCase end end end + + def test_disable_referential_integrity + assert_nothing_raised do + @connection.disable_referential_integrity do + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if @connection.prefetch_primary_key? + id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) + @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" + else + @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + end + end + end + end end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index eee771ecff..2a89430da9 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -13,6 +13,16 @@ class MysqlConnectionTest < ActiveRecord::TestCase end end + def test_connect_with_url + run_without_connection do |orig| + ar_config = ARTest.connection_config['arunit'] + url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}" + klass = Class.new(ActiveRecord::Base) + klass.establish_connection(url) + assert_equal ar_config['database'], klass.connection.current_database + end + end + def test_mysql_reconnect_attribute_after_connection_with_reconnect_false run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false})) diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb new file mode 100644 index 0000000000..21b97b3b39 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -0,0 +1,14 @@ +require "cases/helper" + +module ActiveRecord + class PostgresqlConnectionTest < ActiveRecord::TestCase + def setup + super + @connection = ActiveRecord::Base.connection + end + + def test_encoding + assert_not_nil @connection.encoding + end + end +end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 0d9db92447..e0152e7ccf 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -67,7 +67,7 @@ module ActiveRecord def test_type_cast_bigdecimal bd = BigDecimal.new '10.0' - assert_equal bd.to_s('F'), @conn.type_cast(bd, nil) + assert_equal bd.to_f, @conn.type_cast(bd, nil) end def test_type_cast_unknown diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 4190f6d76d..818902beb5 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -681,4 +681,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase firm = client.create_firm! :name => "baa" assert_equal firm.id, client.client_of end + + def test_self_referential_belongs_to_with_counter_cache_assigning_nil + comment = Comment.create! :post => posts(:thinking), :body => "fuu" + comment.parent = nil + comment.save! + + assert_equal nil, comment.reload.parent + assert_equal 0, comments(:greetings).reload.children_count + end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 325fc58958..c6e451fc57 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1040,4 +1040,24 @@ class EagerAssociationTest < ActiveRecord::TestCase } assert_no_queries { assert_equal groucho, sponsor.thing } end + + def test_joins_with_includes_should_preload_via_joins + post = assert_queries(1) { Post.includes(:comments).joins(:comments).order('posts.id desc').to_a.first } + + assert_queries(0) do + assert_not_equal 0, post.comments.to_a.count + end + end + + def test_join_eager_with_empty_order_should_generate_valid_sql + assert_nothing_raised(ActiveRecord::StatementInvalid) do + Post.includes(:comments).order("").where(:comments => {:body => "Thank you for the welcome"}).first + end + end + + def test_join_eager_with_nil_order_should_generate_valid_sql + assert_nothing_raised(ActiveRecord::StatementInvalid) do + Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first + end + end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 9feb7b010f..607c87beec 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -488,11 +488,11 @@ class BasicsTest < ActiveRecord::TestCase def test_hashing assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] end - + def test_comparison topic_1 = Topic.create! topic_2 = Topic.create! - + assert_equal [topic_2, topic_1].sort, [topic_1, topic_2] end @@ -1113,6 +1113,17 @@ class BasicsTest < ActiveRecord::TestCase self.table_name = 'numeric_data' end + def test_big_decimal_conditions + m = NumericData.new( + :bank_balance => 1586.43, + :big_bank_balance => BigDecimal("1000234000567.95"), + :world_population => 6000000000, + :my_house_population => 3 + ) + assert m.save + assert_equal 0, NumericData.where("bank_balance > ?", 2000.0).count + end + def test_numeric_fields m = NumericData.new( :bank_balance => 1586.43, @@ -1835,4 +1846,29 @@ class BasicsTest < ActiveRecord::TestCase def test_attribtue_names_on_abstract_class assert_equal [], AbstractCompany.attribute_names end + + def test_cache_key_for_existing_record_is_not_timezone_dependent + ActiveRecord::Base.time_zone_aware_attributes = true + + Time.zone = "UTC" + utc_key = Developer.first.cache_key + + Time.zone = "EST" + est_key = Developer.first.cache_key + + assert_equal utc_key, est_key + ensure + ActiveRecord::Base.time_zone_aware_attributes = false + end + + def test_cache_key_format_for_existing_record_with_updated_at + dev = Developer.first + assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key + end + + def test_cache_key_format_for_existing_record_with_nil_updated_at + dev = Developer.first + dev.update_attribute(:updated_at, nil) + assert_match /\/#{dev.id}$/, dev.cache_key + end end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 50ddf6c757..a35baee4ed 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -100,4 +100,20 @@ class EachTest < ActiveRecord::TestCase end end end + + def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified + not_a_post = "not a post" + not_a_post.stubs(:id).raises(StandardError, "not_a_post had #id called on it") + + assert_nothing_raised do + Post.find_in_batches(:batch_size => 1) do |batch| + assert_kind_of Array, batch + assert_kind_of Post, batch.first + + batch.map! { not_a_post } + end + end + + end + end diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index a1d1177289..f554ceef35 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -25,6 +25,40 @@ module ActiveRecord assert ActiveRecord::Base.connection_handler.active_connections? end + class FakeBase < ActiveRecord::Base + def self.establish_connection spec + String === spec ? super : spec + end + end + + def test_url_host_no_db + spec = FakeBase.establish_connection 'postgres://foo?encoding=utf8' + assert_equal({ + :adapter => "postgresql", + :database => "", + :host => "foo", + :encoding => "utf8" }, spec) + end + + def test_url_host_db + spec = FakeBase.establish_connection 'postgres://foo/bar?encoding=utf8' + assert_equal({ + :adapter => "postgresql", + :database => "bar", + :host => "foo", + :encoding => "utf8" }, spec) + end + + def test_url_port + spec = FakeBase.establish_connection 'postgres://foo:123?encoding=utf8' + assert_equal({ + :adapter => "postgresql", + :database => "", + :port => 123, + :host => "foo", + :encoding => "utf8" }, spec) + end + def test_app_delegation manager = ConnectionManagement.new(@app) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 4e75eafe3d..5dc5f99582 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -140,23 +140,30 @@ class FinderTest < ActiveRecord::TestCase def test_find_with_group - developers = Developer.find(:all, :group => "salary", :select => "salary") + developers = Developer.find(:all, :group => "salary", :select => "salary") assert_equal 4, developers.size assert_equal 4, developers.map(&:salary).uniq.size end def test_find_with_group_and_having - developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary") + developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary") assert_equal 3, developers.size assert_equal 3, developers.map(&:salary).uniq.size - assert developers.all? { |developer| developer.salary > 10000 } + assert developers.all? { |developer| developer.salary > 10000 } end def test_find_with_group_and_sanitized_having - developers = Developer.find(:all, :group => "salary", :having => ["sum(salary) > ?", 10000], :select => "salary") + developers = Developer.find(:all, :group => "salary", :having => ["sum(salary) > ?", 10000], :select => "salary") assert_equal 3, developers.size assert_equal 3, developers.map(&:salary).uniq.size - assert developers.all? { |developer| developer.salary > 10000 } + assert developers.all? { |developer| developer.salary > 10000 } + end + + def test_find_with_group_and_sanitized_having_method + developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').all + assert_equal 3, developers.size + assert_equal 3, developers.map(&:salary).uniq.size + assert developers.all? { |developer| developer.salary > 10000 } end def test_find_with_entire_select_statement @@ -659,6 +666,10 @@ class FinderTest < ActiveRecord::TestCase assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary") end + def test_find_by_two_attributes_but_passing_only_one + assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") } + end + def test_find_last_by_one_attribute assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title) assert_nil Topic.find_last_by_title("A title with no matches") @@ -940,6 +951,10 @@ class FinderTest < ActiveRecord::TestCase assert !another.persisted? end + def test_find_or_initialize_from_two_attributes_but_passing_only_one + assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") } + end + def test_find_or_initialize_from_one_aggregate_attribute_and_one_not new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth") assert_equal 123, new_customer.balance.amount diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index c6c6079490..9e8475465e 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -63,6 +63,14 @@ class LogSubscriberTest < ActiveRecord::TestCase assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last) end + def test_exists_query_logging + Developer.exists? 1 + wait + assert_equal 1, @logger.logged(:debug).size + assert_match(/Developer Exists/, @logger.logged(:debug).last) + assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last) + end + def test_cached_queries ActiveRecord::Base.cache do Developer.all diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 2afe3c8f32..ed0240cada 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -497,14 +497,24 @@ end class DynamicScopeTest < ActiveRecord::TestCase fixtures :posts + def setup + @test_klass = Class.new(Post) do + def self.name; "Post"; end + end + end + def test_dynamic_scope - assert_equal Post.scoped_by_author_id(1).find(1), Post.find(1) - assert_equal Post.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, Post.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"}) + assert_equal @test_klass.scoped_by_author_id(1).find(1), @test_klass.find(1) + assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"}) end def test_dynamic_scope_should_create_methods_after_hitting_method_missing - assert_blank Developer.methods.grep(/scoped_by_created_at/) - Developer.scoped_by_created_at(nil) - assert_present Developer.methods.grep(/scoped_by_created_at/) + assert_blank @test_klass.methods.grep(/scoped_by_type/) + @test_klass.scoped_by_type(nil) + assert_present @test_klass.methods.grep(/scoped_by_type/) + end + + def test_dynamic_scope_with_less_number_of_arguments + assert_raise(ArgumentError){ @test_klass.scoped_by_author_id_and_title(1) } end end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 6568eb1d18..67a9ed6cd8 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -755,6 +755,11 @@ module NestedAttributesOnACollectionAssociationTests Interest.reflect_on_association(:man).options[:inverse_of] = :interests end + def test_can_use_symbols_as_object_identifier + @pirate.attributes = { :parrots_attributes => { :foo => { :name => 'Lovely Day' }, :bar => { :name => 'Blown Away' } } } + assert_nothing_raised(NoMethodError) { @pirate.save! } + end + private def association_setter diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 05a41d8a0a..7e3da145e5 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -26,7 +26,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase def test_to_key_with_primary_key_after_destroy topic = Topic.find(1) topic.destroy - assert_equal [1], topic.to_key + assert_equal nil, topic.to_key end def test_integer_key diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 677d659f39..382d659289 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -1,13 +1,8 @@ require "cases/helper" require 'models/contact' require 'models/topic' -require 'models/reply' -require 'models/company' class SerializationTest < ActiveRecord::TestCase - - fixtures :topics, :companies, :accounts - FORMATS = [ :xml, :json ] def setup @@ -19,8 +14,6 @@ class SerializationTest < ActiveRecord::TestCase :awesome => false, :preferences => { :gem => '<strong>ruby</strong>' } } - - @contact = Contact.new(@contact_attributes) end def test_serialized_init_with @@ -29,134 +22,6 @@ class SerializationTest < ActiveRecord::TestCase assert_equal 'foo', topic.content end - def test_to_xml - xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) - bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema - written_on_in_current_timezone = topics(:first).written_on.xmlschema - last_read_in_current_timezone = topics(:first).last_read.xmlschema - - assert_equal "topic", xml.root.name - assert_equal "The First Topic" , xml.elements["//title"].text - assert_equal "David" , xml.elements["//author-name"].text - assert_match "Have a nice day", xml.elements["//content"].text - - assert_equal "1", xml.elements["//id"].text - assert_equal "integer" , xml.elements["//id"].attributes['type'] - - assert_equal "1", xml.elements["//replies-count"].text - assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] - - assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text - assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] - - assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text - - assert_equal nil, xml.elements["//parent-id"].text - assert_equal "integer", xml.elements["//parent-id"].attributes['type'] - assert_equal "true", xml.elements["//parent-id"].attributes['nil'] - - if current_adapter?(:SybaseAdapter) - assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text - assert_equal "datetime" , xml.elements["//last-read"].attributes['type'] - else - # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) - assert_equal "2004-04-15", xml.elements["//last-read"].text - assert_equal "date" , xml.elements["//last-read"].attributes['type'] - end - - # Oracle and DB2 don't have true boolean or time-only fields - unless current_adapter?(:OracleAdapter, :DB2Adapter) - assert_equal "false", xml.elements["//approved"].text - assert_equal "boolean" , xml.elements["//approved"].attributes['type'] - - assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text - assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type'] - end - end - - def test_to_xml_skipping_attributes - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) - assert_equal "<topic>", xml.first(7) - assert !xml.include?(%(<title>The First Topic</title>)) - assert xml.include?(%(<author-name>David</author-name>)) - - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) - assert !xml.include?(%(<title>The First Topic</title>)) - assert !xml.include?(%(<author-name>David</author-name>)) - end - - def test_to_xml_including_has_many_association - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) - assert_equal "<topic>", xml.first(7) - assert xml.include?(%(<replies type="array"><reply>)) - assert xml.include?(%(<title>The Second Topic of the day</title>)) - end - - def test_array_to_xml_including_has_many_association - xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies) - assert xml.include?(%(<replies type="array"><reply>)) - end - - def test_array_to_xml_including_methods - xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ]) - assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml - assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml - end - - def test_array_to_xml_including_has_one_association - xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) - assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) - end - - def test_array_to_xml_including_belongs_to_association - xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) - end - - def test_to_xml_including_belongs_to_association - xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert !xml.include?("<firm>") - - xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert xml.include?("<firm>") - end - - def test_to_xml_including_multiple_associations - xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) - assert_equal "<firm>", xml.first(6) - assert xml.include?(%(<account>)) - assert xml.include?(%(<clients type="array"><client>)) - end - - def test_to_xml_including_multiple_associations_with_options - xml = companies(:first_firm).to_xml( - :indent => 0, :skip_instruct => true, - :include => { :clients => { :only => :name } } - ) - - assert_equal "<firm>", xml.first(6) - assert xml.include?(%(<client><name>Summit</name></client>)) - assert xml.include?(%(<clients type="array"><client>)) - end - - def test_to_xml_including_methods - xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true) - assert_equal "<company>", xml.first(9) - assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>)) - end - - def test_to_xml_with_block - value = "Rockin' the block" - xml = Company.new.to_xml(:skip_instruct => true) do |_xml| - _xml.tag! "arbitrary-element", value - end - assert_equal "<company>", xml.first(9) - assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>)) - end - def test_serialize_should_be_reversible for format in FORMATS @serialized = Contact.new.send("to_#{format}") @@ -184,11 +49,4 @@ class SerializationTest < ActiveRecord::TestCase assert_equal @contact_attributes[:awesome], contact.awesome, "For #{format}" end end - - def test_serialize_should_xml_skip_instruct_for_included_records - @contact.alternative = Contact.new(:name => 'Copa Cabana') - @serialized = @contact.to_xml(:include => [ :alternative ]) - assert_equal @serialized.index('<?xml '), 0 - assert_nil @serialized.index('<?xml ', 1) - end end diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb index 756c8a32eb..88751a72f9 100644 --- a/activerecord/test/cases/xml_serialization_test.rb +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -5,6 +5,9 @@ require 'models/author' require 'models/comment' require 'models/company_in_module' require 'models/toy' +require 'models/topic' +require 'models/reply' +require 'models/company' class XmlSerializationTest < ActiveRecord::TestCase def test_should_serialize_default_root @@ -50,6 +53,23 @@ class XmlSerializationTest < ActiveRecord::TestCase end assert_match %r{<creator>David</creator>}, @xml end + + def test_to_xml_with_block + value = "Rockin' the block" + xml = Contact.new.to_xml(:skip_instruct => true) do |_xml| + _xml.tag! "arbitrary-element", value + end + assert_equal "<contact>", xml.first(9) + assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>)) + end + + def test_should_skip_instruct_for_included_records + @contact = Contact.new + @contact.alternative = Contact.new(:name => 'Copa Cabana') + @xml = @contact.to_xml(:include => [ :alternative ]) + assert_equal @xml.index('<?xml '), 0 + assert_nil @xml.index('<?xml ', 1) + end end class DefaultXmlSerializationTest < ActiveRecord::TestCase @@ -148,7 +168,63 @@ class NilXmlSerializationTest < ActiveRecord::TestCase end class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase - fixtures :authors, :posts, :projects + fixtures :topics, :companies, :accounts, :authors, :posts, :projects + + def test_to_xml + xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) + bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema + written_on_in_current_timezone = topics(:first).written_on.xmlschema + last_read_in_current_timezone = topics(:first).last_read.xmlschema + + assert_equal "topic", xml.root.name + assert_equal "The First Topic" , xml.elements["//title"].text + assert_equal "David" , xml.elements["//author-name"].text + assert_match "Have a nice day", xml.elements["//content"].text + + assert_equal "1", xml.elements["//id"].text + assert_equal "integer" , xml.elements["//id"].attributes['type'] + + assert_equal "1", xml.elements["//replies-count"].text + assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] + + assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text + assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] + + assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text + + assert_equal nil, xml.elements["//parent-id"].text + assert_equal "integer", xml.elements["//parent-id"].attributes['type'] + assert_equal "true", xml.elements["//parent-id"].attributes['nil'] + + if current_adapter?(:SybaseAdapter) + assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text + assert_equal "datetime" , xml.elements["//last-read"].attributes['type'] + else + # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) + assert_equal "2004-04-15", xml.elements["//last-read"].text + assert_equal "date" , xml.elements["//last-read"].attributes['type'] + end + + # Oracle and DB2 don't have true boolean or time-only fields + unless current_adapter?(:OracleAdapter, :DB2Adapter) + assert_equal "false", xml.elements["//approved"].text + assert_equal "boolean" , xml.elements["//approved"].attributes['type'] + + assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text + assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type'] + end + end + + def test_except_option + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) + assert_equal "<topic>", xml.first(7) + assert !xml.include?(%(<title>The First Topic</title>)) + assert xml.include?(%(<author-name>David</author-name>)) + + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) + assert !xml.include?(%(<title>The First Topic</title>)) + assert !xml.include?(%(<author-name>David</author-name>)) + end # to_xml used to mess with the hash the user provided which # caused the builder to be reused. This meant the document kept @@ -184,6 +260,39 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase assert_match %r{<hello-post>}, xml end + def test_including_has_many_association + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) + assert_equal "<topic>", xml.first(7) + assert xml.include?(%(<replies type="array"><reply>)) + assert xml.include?(%(<title>The Second Topic of the day</title>)) + end + + def test_including_belongs_to_association + xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert !xml.include?("<firm>") + + xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert xml.include?("<firm>") + end + + def test_including_multiple_associations + xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) + assert_equal "<firm>", xml.first(6) + assert xml.include?(%(<account>)) + assert xml.include?(%(<clients type="array"><client>)) + end + + def test_including_association_with_options + xml = companies(:first_firm).to_xml( + :indent => 0, :skip_instruct => true, + :include => { :clients => { :only => :name } } + ) + + assert_equal "<firm>", xml.first(6) + assert xml.include?(%(<client><name>Summit</name></client>)) + assert xml.include?(%(<clients type="array"><client>)) + end + def test_methods_are_called_on_object xml = authors(:david).to_xml :methods => :label, :indent => 0 assert_match %r{<label>.*</label>}, xml @@ -265,4 +374,27 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase assert_equal array.size, array.select { |author| author.has_key? 'firstname' }.size end + def test_array_to_xml_including_has_many_association + xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies) + assert xml.include?(%(<replies type="array"><reply>)) + end + + def test_array_to_xml_including_methods + xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ]) + assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml + assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml + end + + def test_array_to_xml_including_has_one_association + xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) + assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) + end + + def test_array_to_xml_including_belongs_to_association + xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) + end end diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 43650c0427..88b139d931 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -7,10 +7,13 @@ class Comment < ActiveRecord::Base :joins => :post, :conditions => { "posts.author_id" => 1 } scope :created - + belongs_to :post, :counter_cache => true has_many :ratings + has_many :children, :class_name => 'Comment', :foreign_key => :parent_id + belongs_to :parent, :class_name => 'Comment', :counter_cache => :children_count + def self.what_are_you 'a comment...' end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index e6cd274586..64e0452100 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -155,6 +155,7 @@ ActiveRecord::Schema.define do end t.string :type t.integer :taggings_count, :default => 0 + t.integer :children_count, :default => 0 end create_table :companies, :force => true do |t| diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 034d03533f..a25720adbf 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,8 @@ *Rails 3.2.0 (unreleased)* +* The definition of blank string for Ruby 1.9 has been extended to Unicode whitespace. +Also, in 1.8 the ideographic space U+3000 is considered to be whitespace. [Akira Matsuda, Damien Mathieu] + * The inflector understands acronyms. [dlee] * Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern [José Valim] diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 64d2c3bff6..e07294178b 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -183,6 +183,14 @@ module ActiveSupport # Provide support for raw values in the local cache strategy. module LocalCacheWithRaw # :nodoc: protected + def read_entry(key, options) + entry = super + if options[:raw] && local_cache && entry + entry = deserialize_entry(entry.value) + end + entry + end + def write_entry(key, entry, options) # :nodoc: retval = super if options[:raw] && local_cache && retval diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 6d7f771b5d..3e05c6eaf2 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -96,7 +96,7 @@ module Enumerable # Returns true if the collection has more than 1 element. Functionally equivalent to collection.size > 1. # Can be called with a block too, much like any?, so people.many? { |p| p.age > 26 } returns true if more than 1 person is over 26. def many?(&block) - size = block_given? ? select(&block).size : self.size + size = block_given? ? count(&block) : self.size size > 1 end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 102378a029..5f07bb4f5a 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -95,7 +95,7 @@ class Hash case value.class.to_s when 'Hash' if value['type'] == 'array' - _, entries = Array.wrap(value.detect { |k,v| k != 'type' }) + _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) }) if entries.nil? || (c = value['__content__'] && c.blank?) [] else diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 1777a4b32d..41f9a76b13 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -127,10 +127,6 @@ class Module end module_eval(<<-EOS, file, line - 5) - if instance_methods(false).map(&:to_s).include?("#{prefix}#{method}") - remove_possible_method("#{prefix}#{method}") - 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 diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 8221dc4abe..fe27f45295 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,3 +1,6 @@ +# encoding: utf-8 +require 'active_support/core_ext/string/encoding' + class Object # An object is blank if it's false, empty, or a whitespace string. # For example, "", " ", +nil+, [], and {} are all blank. @@ -86,14 +89,23 @@ class Hash end class String + # 0x3000: fullwidth whitespace + NON_WHITESPACE_REGEXP = %r![^\s#{[0x3000].pack("U")}]! + # A string is blank if it's empty or contains whitespaces only: # # "".blank? # => true # " ".blank? # => true + # " ".blank? # => true # " something here ".blank? # => false # def blank? - self !~ /\S/ + # 1.8 does not takes [:space:] properly + if encoding_aware? + self !~ /[^[:space:]]/ + else + self !~ NON_WHITESPACE_REGEXP + end end end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 3d092529d6..ec2c717942 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -109,7 +109,7 @@ module ActiveSupport def xmlschema(fraction_digits = 0) fraction = if fraction_digits > 0 - ".%i" % time.usec.to_s[0, fraction_digits] + (".%06i" % time.usec)[0, fraction_digits + 1] end "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}" diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 498127e5bc..8b3e4800c3 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -635,7 +635,14 @@ uses_memcached 'memcached backed store' do cache.write("foo", 2) assert_equal "2", cache.read("foo") end - + + def test_raw_values_with_marshal + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) + cache.clear + cache.write("foo", Marshal.dump([])) + assert_equal [], cache.read("foo") + end + def test_local_cache_raw_values cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) cache.clear @@ -644,6 +651,15 @@ uses_memcached 'memcached backed store' do assert_equal "2", cache.read("foo") end end + + def test_local_cache_raw_values_with_marshal + cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) + cache.clear + cache.with_local_cache do + cache.write("foo", Marshal.dump([])) + assert_equal [], cache.read("foo") + end + end end end diff --git a/activesupport/test/core_ext/blank_test.rb b/activesupport/test/core_ext/blank_test.rb index 97c6b213ba..a2cf298905 100644 --- a/activesupport/test/core_ext/blank_test.rb +++ b/activesupport/test/core_ext/blank_test.rb @@ -1,8 +1,10 @@ +# encoding: utf-8 + require 'abstract_unit' require 'active_support/core_ext/object/blank' class BlankTest < Test::Unit::TestCase - BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", [], {} ] + BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", ' ', [], {} ] NOT = [ EmptyFalse.new, Object.new, true, 0, 1, 'a', [nil], { nil => 0 } ] def test_blank diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 0b3f18faec..1813ba2a4d 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -670,6 +670,55 @@ class HashToXmlTest < Test::Unit::TestCase assert_match %r{<local-created-at type=\"datetime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml end + def test_multiple_records_from_xml_with_attributes_other_than_type_ignores_them_without_exploding + topics_xml = <<-EOT + <topics type="array" page="1" page-count="1000" per-page="2"> + <topic> + <title>The First Topic</title> + <author-name>David</author-name> + <id type="integer">1</id> + <approved type="boolean">false</approved> + <replies-count type="integer">0</replies-count> + <replies-close-in type="integer">2592000000</replies-close-in> + <written-on type="date">2003-07-16</written-on> + <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at> + <content>Have a nice day</content> + <author-email-address>david@loudthinking.com</author-email-address> + <parent-id nil="true"></parent-id> + </topic> + <topic> + <title>The Second Topic</title> + <author-name>Jason</author-name> + <id type="integer">1</id> + <approved type="boolean">false</approved> + <replies-count type="integer">0</replies-count> + <replies-close-in type="integer">2592000000</replies-close-in> + <written-on type="date">2003-07-16</written-on> + <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at> + <content>Have a nice day</content> + <author-email-address>david@loudthinking.com</author-email-address> + <parent-id></parent-id> + </topic> + </topics> + EOT + + expected_topic_hash = { + :title => "The First Topic", + :author_name => "David", + :id => 1, + :approved => false, + :replies_count => 0, + :replies_close_in => 2592000000, + :written_on => Date.new(2003, 7, 16), + :viewed_at => Time.utc(2003, 7, 16, 9, 28), + :content => "Have a nice day", + :author_email_address => "david@loudthinking.com", + :parent_id => nil + }.stringify_keys + + assert_equal expected_topic_hash, Hash.from_xml(topics_xml)["topics"].first + end + def test_single_record_from_xml topic_xml = <<-EOT <topic> diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 4000cc913a..18a86e08f5 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -158,7 +158,7 @@ class StringInflectionsTest < Test::Unit::TestCase assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:local) assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time assert_equal Time.local_time(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:local) - assert_equal Time.utc(2039, 2, 27, 23, 50), "2039-02-27 22:50 -0100".to_time + assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time assert_nil "".to_time end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 72b55183ba..b2309ae806 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -111,6 +111,13 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal "1999-12-31T19:00:00.123456-05:00", @twz.xmlschema(12) end + def test_xmlschema_with_fractional_seconds_lower_than_hundred_thousand + @twz += 0.001234 # advance the time by a fraction + assert_equal "1999-12-31T19:00:00.001-05:00", @twz.xmlschema(3) + assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(6) + assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(12) + end + def test_to_yaml assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml) end diff --git a/ci/travis.rb b/ci/travis.rb new file mode 100755 index 0000000000..8087c72f90 --- /dev/null +++ b/ci/travis.rb @@ -0,0 +1,142 @@ +#!/usr/bin/env ruby +require 'fileutils' +include FileUtils + +commands = [ + 'mysql -e "create database activerecord_unittest;"', + 'mysql -e "create database activerecord_unittest2;"', + 'psql -c "create database activerecord_unittest;" -U postgres', + 'psql -c "create database activerecord_unittest2;" -U postgres' +] + +commands.each do |command| + system("#{command} > /dev/null 2>&1") +end + +class Build + MAP = { + 'railties' => 'railties', + 'ap' => 'actionpack', + 'am' => 'actionmailer', + 'amo' => 'activemodel', + 'ares' => 'activeresource', + 'as' => 'activesupport', + 'ar' => 'activerecord' + } + + attr_reader :component, :options + + def initialize(component, options = {}) + @component = component + @options = options + end + + def run!(options = {}) + self.options.update(options) + Dir.chdir(dir) do + announce(heading) + ENV['IM'] = identity_map?.inspect + rake(*tasks) + end + end + + def announce(heading) + puts "\n\e[1;33m[Travis CI] #{heading}\e[m\n" + end + + def heading + heading = [gem] + heading << "with #{adapter} IM #{identity_map? ? 'enabled' : 'disabled'}" if activerecord? + heading << "in isolation" if isolated? + heading.join(' ') + end + + def tasks + if activerecord? + ['mysql:rebuild_databases', "#{adapter}:#{'isolated_' if isolated?}test"] + else + ["test#{':isolated' if isolated?}"] + end + end + + def key + key = [gem] + key << adapter if activerecord? + key << 'IM' if identity_map? + key << 'isolated' if isolated? + key.join(':') + end + + def activerecord? + gem == 'activerecord' + end + + def identity_map? + options[:identity_map] + end + + def isolated? + options[:isolated] + end + + def gem + MAP[component.split(':').first] + end + alias :dir :gem + + def adapter + component.split(':').last + end + + def rake(*tasks) + tasks.each do |task| + cmd = "bundle exec rake #{task}" + puts "Running command: #{cmd}" + return false unless system(cmd) + end + true + end +end + +results = {} + +ENV['GEM'].split(',').each do |gem| + [false, true].each do |isolated| + next if gem == 'railties' && isolated + + build = Build.new(gem, :isolated => isolated) + results[build.key] = build.run! + + if build.activerecord? + build.options[:identity_map] = true + results[build.key] = build.run! + end + end +end + +# puts +# puts "Build environment:" +# puts " #{`cat /etc/issue`}" +# puts " #{`uname -a`}" +# puts " #{`ruby -v`}" +# puts " #{`mysql --version`}" +# # puts " #{`pg_config --version`}" +# puts " SQLite3: #{`sqlite3 -version`}" +# `gem env`.each_line {|line| print " #{line}"} +# puts " Bundled gems:" +# `bundle show`.each_line {|line| print " #{line}"} +# puts " Local gems:" +# `gem list`.each_line {|line| print " #{line}"} + +failures = results.select { |key, value| value == false } + +if failures.empty? + puts + puts "Rails build finished sucessfully" + exit(true) +else + puts + puts "Rails build FAILED" + puts "Failed components: #{failures.map { |component| component.first }.join(', ')}" + exit(false) +end diff --git a/load_paths.rb b/load_paths.rb index 590af69330..17f5ce180d 100644 --- a/load_paths.rb +++ b/load_paths.rb @@ -1,8 +1,4 @@ # bust gem prelude -if defined? Gem - gem 'bundler' -else - require 'rubygems' -end +require 'rubygems' unless defined? Gem require 'bundler' -Bundler.setup +Bundler.setup
\ No newline at end of file diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb index 14d671c8f3..2219fcea66 100644 --- a/railties/guides/rails_guides/generator.rb +++ b/railties/guides/rails_guides/generator.rb @@ -227,13 +227,13 @@ module RailsGuides end code_blocks.push(<<HTML) -<notextile> +<notextile> <div class="code_container"> <pre class="brush: #{brush}; gutter: false; toolbar: false"> #{ERB::Util.h($2).strip} </pre> </div> -</notextile> +</notextile> HTML "\ndirty_workaround_for_notextile_#{code_blocks.size - 1}\n" end @@ -280,3 +280,4 @@ HTML end end end + diff --git a/railties/guides/rails_guides/textile_extensions.rb b/railties/guides/rails_guides/textile_extensions.rb index 352c5e91dd..dd51e0d66b 100644 --- a/railties/guides/rails_guides/textile_extensions.rb +++ b/railties/guides/rails_guides/textile_extensions.rb @@ -25,7 +25,7 @@ module RailsGuides def plusplus(body) body.gsub!(/\+(.*?)\+/) do |m| - "<notextile><tt>#{$1}</tt></notextile>" + "<notextile><tt>#{$1}</tt></notextile>" end # The real plus sign @@ -36,8 +36,9 @@ module RailsGuides body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain)>(.*?)</\1>}m) do |m| es = ERB::Util.h($2) css_class = $1.in?(['erb', 'shell']) ? 'html' : $1 - %{<notextile><div class="code_container"><code class="#{css_class}">#{es}</code></div></notextile>} + %{<notextile><div class="code_container"><code class="#{css_class}">#{es}</code></div></notextile>} end end end end + diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index a0ed85cf01..b35e04d7e1 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -78,12 +78,14 @@ The following values are considered to be blank in a Rails application: * +nil+ and +false+, -* strings composed only of whitespace, i.e. matching +/\A\s*\z/+, +* strings composed only of whitespace (see note below), * empty arrays and hashes, and * any other object that responds to +empty?+ and it is empty. +INFO: In Ruby 1.9 the predicate for strings uses the Unicode-aware character class <tt>[:space:]</tt>, so for example U+2029 (paragraph separator) is considered to be whitespace. In Ruby 1.8 whitespace is considered to be <tt>\s</tt> together with the ideographic space U+3000. + WARNING: Note that numbers are not mentioned, in particular 0 and 0.0 are *not* blank. For example, this method from +ActionDispatch::Session::AbstractStore+ uses +blank?+ for checking whether a session key is present: diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 57e390f385..68fb22f5d8 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -823,10 +823,10 @@ If you want a complete list of all of the available routes in your application, For example, here's a small section of the +rake routes+ output for a RESTful route: <pre> - users GET /users {:controller=>"users", :action=>"index"} -formatted_users GET /users.:format {:controller=>"users", :action=>"index"} - POST /users {:controller=>"users", :action=>"create"} - POST /users.:format {:controller=>"users", :action=>"create"} + users GET /users(.:format) users#index + POST /users(.:format) users#create + new_user GET /users/new(.:format) users#new +edit_user GET /users/:id/edit(.:format) users#edit </pre> You may restrict the listing to the routes that map to a particular controller setting the +CONTROLLER+ environment variable: diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index df92934457..603ede3d1e 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -4,6 +4,7 @@ require 'pathname' require 'active_support' require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/logger' require 'rails/application' @@ -91,7 +92,7 @@ module Rails # # * The Rails environment; # * The environment variable RAILS_GROUPS; - # * The optional hash given as argument with group dependencies; + # * The optional envs given as argument and the hash with group dependencies; # # == Examples # @@ -101,12 +102,14 @@ module Rails # # => [:default, :development, :assets] for Rails.env == "development" # # => [:default, :production] for Rails.env == "production" # - def groups(hash={}) + def groups(*groups) + hash = groups.extract_options! env = Rails.env - groups = [:default, env] + groups.unshift(:default, env) groups.concat ENV["RAILS_GROUPS"].to_s.split(",") groups.concat hash.map { |k,v| k if v.map(&:to_s).include?(env) } groups.compact! + groups.uniq! groups end diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 692f8f546f..9e2f1a4b7a 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -106,6 +106,15 @@ module Rails self end + # Rails.application.env_config stores some of the Rails initial environment parameters. + # Currently stores: + # + # * action_dispatch.parameter_filter" => config.filter_parameters, + # * action_dispatch.secret_token" => config.secret_token, + # * action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions + # + # These parameters will be used by middlewares and engines to configure themselves. + # def env_config @env_config ||= super.merge({ "action_dispatch.parameter_filter" => config.filter_parameters, diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 1a29483a73..975e159999 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -30,6 +30,7 @@ module Rails @log_level = nil @middleware = app_middleware @generators = app_generators + @cache_store = [ :file_store, "#{root}/tmp/cache/" ] @assets = ActiveSupport::OrderedOptions.new @assets.enabled = false @@ -37,6 +38,7 @@ module Rails @assets.precompile = [ /\w+\.(?!js|css).+/, /application.(css|js)$/ ] @assets.prefix = "/assets" + @assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/" ] @assets.js_compressor = nil @assets.css_compressor = nil end @@ -70,7 +72,6 @@ module Rails paths.add "public/javascripts" paths.add "public/stylesheets" paths.add "tmp" - paths.add "tmp/cache" paths end end @@ -95,16 +96,6 @@ module Rails YAML::load(ERB.new(IO.read(paths["config/database"].first)).result) end - def cache_store - @cache_store ||= begin - if File.exist?("#{root}/tmp/cache/") - [ :file_store, "#{root}/tmp/cache/" ] - else - :memory_store - end - end - end - def log_level @log_level ||= Rails.env.production? ? :info : :debug end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 433a56dc57..b8541c236e 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -92,14 +92,15 @@ module Rails # def environment(data=nil, options={}, &block) sentinel = /class [a-z_:]+ < Rails::Application/i + env_file_sentinel = /::Application\.configure do/ data = block.call if !data && block_given? in_root do if options[:env].nil? inject_into_file 'config/application.rb', "\n #{data}", :after => sentinel, :verbose => false else - Array.wrap(options[:env]).each do|env| - append_file "config/environments/#{env}.rb", "\n#{data}", :verbose => false + Array.wrap(options[:env]).each do |env| + inject_into_file "config/environments/#{env}.rb", "\n #{data}", :after => env_file_sentinel, :verbose => false end end end diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index f9f89c9f1d..816d82cac3 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -32,7 +32,7 @@ module Rails when :decimal then "9.99" when :datetime, :timestamp, :time then Time.now.to_s(:db) when :date then Date.today.to_s(:db) - when :string then "MyString" + when :string then name == "type" ? "" : "MyString" when :text then "MyText" when :boolean then false when :references, :belongs_to then nil diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 6af9d299aa..d9dd33518a 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -117,7 +117,8 @@ module Rails end def tmp - empty_directory_with_gitkeep "tmp/cache" + empty_directory "tmp/cache" + empty_directory "tmp/cache/assets" end def vendor diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index eaa31e7386..7687b1beac 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -8,14 +8,14 @@ require 'rails/all' require "action_controller/railtie" require "action_mailer/railtie" require "active_resource/railtie" -<%= comment_if :skip_sprockets %> require "sprockets/railtie" -<%= comment_if :skip_test_unit %> require "rails/test_unit/railtie" +<%= comment_if :skip_sprockets %>require "sprockets/railtie" +<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie" <% end -%> # If you have a Gemfile, require the default gems, the ones in the # current environment and also include :assets gems if in development # or test environments. -Bundler.require *Rails.groups(:assets => %w(development test)) if defined?(Bundler) +Bundler.require *Rails.groups(:assets) if defined?(Bundler) module <%= app_const_base %> class Application < Rails::Application @@ -47,9 +47,9 @@ module <%= app_const_base %> # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] - <% unless options.skip_sprockets? %> +<% unless options.skip_sprockets? -%> # Enable the asset pipeline config.assets.enabled = true - <% end %> +<% end -%> end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb index 9e8b0131f8..5d8d9be237 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb @@ -8,3 +8,8 @@ # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end +# +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec index 56b06829d8..eb1a1e5054 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec @@ -4,7 +4,7 @@ Gem::Specification.new do |s| s.name = "<%= name %>" s.summary = "Insert <%= camelized %> summary." s.description = "Insert <%= camelized %> description." - s.files = Dir["{app,config,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"] + s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"] <% unless options.skip_test_unit? -%> s.test_files = Dir["test/**/*"] <% end -%> diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile index b28a842731..6ed6adcf1b 100755 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile @@ -15,7 +15,7 @@ end RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_dir = 'rdoc' rdoc.title = '<%= camelized %>' - rdoc.options << '--line-numbers' << '--inline-source' + rdoc.options << '--line-numbers' rdoc.rdoc_files.include('README.rdoc') rdoc.rdoc_files.include('lib/**/*.rb') end @@ -24,3 +24,8 @@ end APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__) load 'rails/tasks/engine.rake' <% end %> + +<% unless options[:skip_gemspec] -%> + +Bundler::GemHelper.install_tasks +<% end %> diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb index 4864ead960..996ea79e67 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb @@ -8,8 +8,8 @@ require 'rails/all' require "action_controller/railtie" require "action_mailer/railtie" require "active_resource/railtie" -<%= comment_if :skip_sprockets %> require "sprockets/railtie" -<%= comment_if :skip_test_unit %> require "rails/test_unit/railtie" +<%= comment_if :skip_sprockets %>require "sprockets/railtie" +<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie" <% end -%> Bundler.require diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index daa1bdfc29..de3d0b6fc5 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -157,7 +157,7 @@ module Rails path = File.expand_path(p, @root.path) if @glob - result.concat Dir[File.join(path, @glob)] + result.concat Dir[File.join(path, @glob)].sort else result << path end diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index 79255d1f56..5d613e0698 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -47,7 +47,7 @@ namespace :doc do rdoc.rdoc_dir = 'doc/app' rdoc.template = ENV['template'] if ENV['template'] rdoc.title = ENV['title'] || "Rails Application Documentation" - rdoc.options << '--line-numbers' << '--inline-source' + rdoc.options << '--line-numbers' rdoc.options << '--charset' << 'utf-8' rdoc.rdoc_files.include('doc/README_FOR_APP') rdoc.rdoc_files.include('app/**/*.rb') @@ -60,7 +60,7 @@ namespace :doc do rdoc.rdoc_dir = 'doc/api' rdoc.template = "#{ENV['template']}.rb" if ENV['template'] rdoc.title = "Rails Framework Documentation" - rdoc.options << '--line-numbers' << '--inline-source' + rdoc.options << '--line-numbers' rdoc.rdoc_files.include('README') gem_path('actionmailer') do |actionmailer| @@ -133,7 +133,7 @@ namespace :doc do files = Rake::FileList.new options << "-o doc/plugins/#{plugin}" options << "--title '#{plugin.titlecase} Plugin Documentation'" - options << '--line-numbers' << '--inline-source' + options << '--line-numbers' options << '--charset' << 'utf-8' options << '-T html' diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index a0c953967c..0c26bcf790 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -10,8 +10,16 @@ task :routes => :environment do routes = all_routes.collect do |route| reqs = route.requirements.dup - reqs[:to] = route.app unless route.app.class.name.to_s =~ /^ActionDispatch::Routing/ - reqs = reqs.empty? ? "" : reqs.inspect + rack_app = route.app unless route.app.class.name.to_s =~ /^ActionDispatch::Routing/ + + endpoint = rack_app ? rack_app.inspect : "#{reqs[:controller]}##{reqs[:action]}" + constraints = reqs.except(:controller, :action) + + reqs = endpoint == '#' ? '' : endpoint + + unless constraints.empty? + reqs = reqs.empty? ? constraints.inspect : "#{reqs} #{constraints.inspect}" + end {:name => route.name.to_s, :verb => route.verb.to_s, :path => route.path, :reqs => reqs} end diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake index 3ee5452475..0d6c10328f 100644 --- a/railties/lib/rails/tasks/tmp.rake +++ b/railties/lib/rails/tasks/tmp.rake @@ -4,7 +4,7 @@ namespace :tmp do desc "Creates tmp directories for sessions, cache, sockets, and pids" task :create do - FileUtils.mkdir_p(%w( tmp/sessions tmp/cache tmp/sockets tmp/pids )) + FileUtils.mkdir_p(%w( tmp/sessions tmp/cache tmp/sockets tmp/pids tmp/cache/assets )) end namespace :sessions do diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 2547863d12..448982f9de 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -45,7 +45,7 @@ module ApplicationTests Rails.env = "development" assert_equal [:default, "development"], Rails.groups assert_equal [:default, "development", :assets], Rails.groups(:assets => [:development]) - assert_equal [:default, "development", :assets], Rails.groups(:assets => %w(development)) + assert_equal [:default, "development", :another, :assets], Rails.groups(:another, :assets => %w(development)) Rails.env = "test" assert_equal [:default, "test"], Rails.groups(:assets => [:development]) @@ -516,5 +516,14 @@ module ApplicationTests get "/", { :format => :xml }, "HTTP_ACCEPT" => "application/xml" assert_equal 'XML', last_response.body end + + test "Rails.application#env_config exists and include some existing parameters" do + make_basic_app + + assert_respond_to app, :env_config + assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters + assert_equal app.env_config['action_dispatch.secret_token'], app.config.secret_token + assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions + end end end diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index 0d22d8c19a..964cff48cd 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -48,7 +48,6 @@ module ApplicationTests assert_path @paths["vendor"], "vendor" assert_path @paths["vendor/plugins"], "vendor/plugins" assert_path @paths["tmp"], "tmp" - assert_path @paths["tmp/cache"], "tmp/cache" assert_path @paths["config"], "config" assert_path @paths["config/locales"], "config/locales/en.yml" assert_path @paths["config/environment"], "config/environment.rb" diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 7671c129e9..cc65a674c9 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -91,7 +91,7 @@ module ApplicationTests get '/cart', :to => 'cart#show' end RUBY - assert_match 'cart GET /cart(.:format)', Dir.chdir(app_path){ `rake routes` } + assert_equal "cart GET /cart(.:format) cart#show\n", Dir.chdir(app_path){ `rake routes` } end def test_rake_routes_shows_custom_assets @@ -100,7 +100,91 @@ module ApplicationTests get '/custom/assets', :to => 'custom_assets#show' end RUBY - assert_match 'custom_assets GET /custom/assets(.:format)', Dir.chdir(app_path){ `rake routes` } + assert_equal "custom_assets GET /custom/assets(.:format) custom_assets#show\n", + Dir.chdir(app_path){ `rake routes` } + end + + def test_rake_routes_shows_resources_route + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + resources :articles + end + RUBY + expected = + " articles GET /articles(.:format) articles#index\n" << + " POST /articles(.:format) articles#create\n" << + " new_article GET /articles/new(.:format) articles#new\n" << + "edit_article GET /articles/:id/edit(.:format) articles#edit\n" << + " article GET /articles/:id(.:format) articles#show\n" << + " PUT /articles/:id(.:format) articles#update\n" << + " DELETE /articles/:id(.:format) articles#destroy\n" + assert_equal expected, Dir.chdir(app_path){ `rake routes` } + end + + def test_rake_routes_shows_root_route + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + root :to => 'pages#main' + end + RUBY + assert_equal "root / pages#main\n", Dir.chdir(app_path){ `rake routes` } + end + + def test_rake_routes_shows_controller_and_action_only_route + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + match ':controller/:action' + end + RUBY + assert_equal " /:controller/:action(.:format) \n", Dir.chdir(app_path){ `rake routes` } + end + + def test_rake_routes_shows_controller_and_action_route_with_constraints + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + match ':controller(/:action(/:id))', :id => /\\d+/ + end + RUBY + assert_equal " /:controller(/:action(/:id))(.:format) {:id=>/\\d+/}\n", Dir.chdir(app_path){ `rake routes` } + end + + def test_rake_routes_shows_route_with_defaults + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + match 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'} + end + RUBY + assert_equal %Q[ /photos/:id(.:format) photos#show {:format=>"jpg"}\n], Dir.chdir(app_path){ `rake routes` } + end + + def test_rake_routes_shows_route_with_constraints + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + match 'photos/:id' => 'photos#show', :id => /[A-Z]\\d{5}/ + end + RUBY + assert_equal " /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}\n", Dir.chdir(app_path){ `rake routes` } + end + + def test_rake_routes_shows_route_with_rack_app + app_file "lib/rack_app.rb", <<-RUBY + class RackApp + class << self + def call(env) + end + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + require 'rack_app' + + AppTemplate::Application.routes.draw do + match 'foo/:id' => RackApp, :id => /[A-Z]\\d{5}/ + end + RUBY + + assert_equal " /foo/:id(.:format) RackApp {:id=>/[A-Z]\\d{5}/}\n", Dir.chdir(app_path){ `rake routes` } end def test_logger_is_flushed_when_exiting_production_rake_tasks diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 597746c4aa..e4a8000425 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -109,6 +109,13 @@ class ActionsTest < Rails::Generators::TestCase assert_file 'config/application.rb', /#{Regexp.escape(autoload_paths)}/ end + def test_environment_should_include_data_in_environment_initializer_block_with_env_option + run_generator + autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]' + action :environment, autoload_paths, :env => 'development' + assert_file "config/environments/development.rb", /Application\.configure do\n #{Regexp.escape(autoload_paths)}/ + end + def test_environment_with_block_should_include_block_contents_in_environment_initializer_block run_generator diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 81f0bf5e82..fb7ebaa1fa 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -35,6 +35,7 @@ DEFAULT_APP_FILES = %w( vendor/assets vendor/plugins tmp/cache + tmp/cache/assets ) class AppGeneratorTest < Rails::Generators::TestCase diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index c9f8ab0a7b..a85829085c 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -68,6 +68,11 @@ class GeneratedAttributeTest < Rails::Generators::TestCase assert_field_default_value :string, 'MyString' end + def test_default_value_for_type + att = Rails::Generators::GeneratedAttribute.new("type", "string") + assert_equal("", att.default) + end + def test_default_value_is_text assert_field_default_value :text, 'MyText' end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 4bd77ff7e3..0ccb2ae9da 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -206,7 +206,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_creating_gemspec run_generator assert_file "bukkits.gemspec", /s.name = "bukkits"/ - assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,lib\}\/\*\*\/\*"\]/ + assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*"\]/ assert_file "bukkits.gemspec", /s.test_files = Dir\["test\/\*\*\/\*"\]/ assert_file "bukkits.gemspec", /s.version = "0.0.1"/ end |