diff options
author | José Valim <jose.valim@gmail.com> | 2009-11-23 22:04:18 -0200 |
---|---|---|
committer | José Valim <jose.valim@gmail.com> | 2009-11-23 22:04:18 -0200 |
commit | 4ff66b6b85d1351e447f18c027f1dba6bcf23a7b (patch) | |
tree | a46d6c65c5c67964bc8658bf991157f0f8056f55 | |
parent | 01ae99c681d31803f3a29f8305c9a041aa456660 (diff) | |
parent | 934bb012ba3f1da5cd181ae5c2d84f697a3c58a1 (diff) | |
download | rails-4ff66b6b85d1351e447f18c027f1dba6bcf23a7b.tar.gz rails-4ff66b6b85d1351e447f18c027f1dba6bcf23a7b.tar.bz2 rails-4ff66b6b85d1351e447f18c027f1dba6bcf23a7b.zip |
Merge branch 'master' of git://github.com/rails/rails
88 files changed, 907 insertions, 1537 deletions
diff --git a/.gitignore b/.gitignore index 3b922f29f7..70b7c0057a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ railties/guides/output *.rbc *.swp *.swo +*.tmproj bin vendor/gems/ railties/tmp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..20ed3ed9c9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "arel"] + path = arel + url = git://github.com/rails/arel.git +[submodule "rack-mount"] + path = rack-mount + url = git://github.com/rails/rack-mount.git @@ -1,6 +1,3 @@ -clear_sources -source 'http://gemcutter.org' - gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" @@ -12,12 +9,11 @@ end # AR gem "arel", "0.2.pre", :git => "git://github.com/rails/arel.git" gem "sqlite3-ruby", ">= 1.2.5" +gem "pg", ">= 0.8.0" +gem "mysql", ">= 2.8.1" # AP gem "rack", "1.0.1", :git => "git://github.com/rails/rack.git" -gem "rack-mount", :git => "git://github.com/rails/rack-mount.git" -gem "rack-test", ">= 0.5.0" -gem "erubis", ">= 2.6.5" gem "RedCloth", ">= 4.2.2" if ENV['CI'] @@ -25,8 +21,6 @@ if ENV['CI'] gem "nokogiri", ">= 1.4.0" gem "memcache-client", ">= 1.7.6" - gem "pg", ">= 0.8.0" - gem "mysql", ">= 2.8.1" # fcgi gem doesn't compile on 1.9 # avoid minitest strangeness on 1.9 diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index ae5985120e..78286f6747 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -18,8 +18,8 @@ Gem::Specification.new do |s| s.add_dependency('activemodel', '= 3.0.pre') s.add_dependency('rack', '~> 1.0.1') s.add_dependency('rack-test', '~> 0.5.0') - s.add_dependency('rack-mount', '~> 0.0.1') - s.add_dependency('erubis', '~> 2.6.0') + s.add_dependency('rack-mount', '~> 0.2') + s.add_dependency('erubis', '~> 2.6.5') s.require_path = 'lib' s.autorequire = 'action_controller' diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 083d6328af..3caf759032 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -26,6 +26,7 @@ module ActionController #:nodoc: # config.action_controller.cache_store = :file_store, "/path/to/cache/directory" # config.action_controller.cache_store = :drb_store, "druby://localhost:9192" # config.action_controller.cache_store = :mem_cache_store, "localhost" + # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new("localhost:11211") # config.action_controller.cache_store = MyOwnStore.new("parameter") module Caching extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 113c20a758..173df79ee7 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -101,6 +101,11 @@ module ActionController #:nodoc: session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32) end + # The form's authenticity parameter. Override to provide your own. + def form_authenticity_param + params[request_forgery_protection_token] + end + def protect_against_forgery? allow_forgery_protection end diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index c6e847ba0f..e8e88e7479 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -14,12 +14,11 @@ module ActionController #:nodoc: # # When a request comes, for example with format :xml, three steps happen: # - # 1) respond_with searches for a template at people/index.xml; + # 1) responder searches for a template at people/index.xml; # - # 2) if the template is not available, it will create a responder, passing - # the controller and the resource and invoke :to_xml on it; + # 2) if the template is not available, it will invoke :to_xml in the given resource; # - # 3) if the responder does not respond_to :to_xml, call to_format on it. + # 3) if the responder does not respond_to :to_xml, call :to_format on it. # # === Builtin HTTP verb semantics # @@ -88,14 +87,16 @@ module ActionController #:nodoc: @resource = resources.is_a?(Array) ? resources.last : resources @resources = resources @options = options + @action = options.delete(:action) @default_response = options.delete(:default_response) end delegate :head, :render, :redirect_to, :to => :controller delegate :get?, :post?, :put?, :delete?, :to => :request - # Undefine :to_json since it's defined on Object + # Undefine :to_json and :to_yaml since it's defined on Object undef_method(:to_json) if method_defined?(:to_json) + undef_method(:to_yaml) if method_defined?(:to_yaml) # Initializes a new responder an invoke the proper format. If the format is # not defined, call to_format. @@ -111,14 +112,8 @@ module ActionController #:nodoc: # def to_html default_render - rescue ActionView::MissingTemplate - if get? - raise - elsif has_errors? - render :action => default_action - else - redirect_to resource_location - end + rescue ActionView::MissingTemplate => e + navigation_behavior(e) end # All others formats follow the procedure below. First we try to render a @@ -127,9 +122,26 @@ module ActionController #:nodoc: # def to_format default_render - rescue ActionView::MissingTemplate + rescue ActionView::MissingTemplate => e raise unless resourceful? + api_behavior(e) + end + protected + + # This is the common behavior for "navigation" requests, like :html, :iphone and so forth. + def navigation_behavior(error) + if get? + raise error + elsif has_errors? + render :action => default_action + else + redirect_to resource_location + end + end + + # This is the common behavior for "API" requests, like :xml and :json. + def api_behavior(error) if get? display resource elsif has_errors? @@ -141,8 +153,6 @@ module ActionController #:nodoc: end end - protected - # Checks whether the resource responds to the current format or not. # def resourceful? @@ -194,7 +204,7 @@ module ActionController #:nodoc: # the verb is post. # def default_action - request.post? ? :new : :edit + @action || (request.post? ? :new : :edit) end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index b3bb8c623f..6a52854961 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -489,7 +489,7 @@ EOM def self.extended(object) object.class_eval do attr_accessor :original_path, :content_type - alias_method :local_path, :path + alias_method :local_path, :path if method_defined?(:path) end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index ebfb4c9be2..6e112c9b54 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -52,30 +52,38 @@ module ActionDispatch resource = resources.pop + plural = resource.to_s + singular = plural.singularize + if @scope[:scope_level] == :resources - member do - resources(resource, options, &block) + parent_resource = @scope[:scope_level_options][:name] + with_scope_level(:member) do + scope(":#{parent_resource}_id", :name_prefix => parent_resource) do + resources(resource, options, &block) + end end return self end - plural = resource.to_s - singular = plural.singularize + if @scope[:options] && (prefix = @scope[:options][:name_prefix]) + plural = "#{prefix}_#{plural}" + singular = "#{prefix}_#{singular}" + end controller(resource) do namespace(resource) do - with_scope_level(:resources) do + with_scope_level(:resources, :name => singular) do yield if block_given? member do - get "", :to => :show, :as => "#{singular}" + get "", :to => :show, :as => singular put "", :to => :update delete "", :to => :destroy get "edit", :to => :edit, :as => "edit_#{singular}" end collection do - get "", :to => :index, :as => "#{plural}" + get "", :to => :index, :as => plural post "", :to => :create get "new", :to => :new, :as => "new_#{singular}" end @@ -127,11 +135,13 @@ module ActionDispatch end private - def with_scope_level(kind) + def with_scope_level(kind, options = {}) old, @scope[:scope_level] = @scope[:scope_level], kind + old_options, @scope[:scope_level_options] = @scope[:scope_level_options], options yield ensure @scope[:scope_level] = old + @scope[:scope_level_options] = old_options end end @@ -195,9 +205,9 @@ module ActionDispatch @constraints.each { |constraint| if constraint.respond_to?(:matches?) && !constraint.matches?(req) - return Rack::Mount::Const::EXPECTATION_FAILED_RESPONSE + return [417, {}, []] elsif constraint.respond_to?(:call) && !constraint.call(req) - return Rack::Mount::Const::EXPECTATION_FAILED_RESPONSE + return [417, {}, []] end } diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 28e5b806da..c15aaceb5b 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -5,7 +5,7 @@ module ActionDispatch module Routing class RouteSet #:nodoc: NotFound = lambda { |env| - raise ActionController::RoutingError, "No route matches #{env[::Rack::Mount::Const::PATH_INFO].inspect} with #{env.inspect}" + raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect} with #{env.inspect}" } PARAMETERS_KEY = 'action_dispatch.request.path_parameters' @@ -372,7 +372,17 @@ module ActionDispatch end recall[:action] = options.delete(:action) if options[:action] == 'index' - path = _uri(named_route, options, recall) + parameterize = lambda { |name, value| + if name == :controller + value + elsif value.is_a?(Array) + value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/') + else + Rack::Mount::Utils.escape_uri(value.to_param) + end + } + + path = @set.url(named_route, options, recall, :parameterize => parameterize) if path && method == :generate_extras uri = URI(path) extras = uri.query ? @@ -439,59 +449,6 @@ module ActionDispatch def extract_request_environment(request) { :method => request.method } end - - private - def _uri(named_route, params, recall) - params = URISegment.wrap_values(params) - recall = URISegment.wrap_values(recall) - - unless result = @set.generate(:path_info, named_route, params, recall) - return - end - - uri, params = result - params.each do |k, v| - if v._value - params[k] = v._value - else - params.delete(k) - end - end - - uri << "?#{Rack::Mount::Utils.build_nested_query(params)}" if uri && params.any? - uri - end - - class URISegment < Struct.new(:_value, :_escape) - EXCLUDED = [:controller] - - def self.wrap_values(hash) - hash.inject({}) { |h, (k, v)| - h[k] = new(v, !EXCLUDED.include?(k.to_sym)) - h - } - end - - extend Forwardable - def_delegators :_value, :==, :eql?, :hash - - def to_param - @to_param ||= begin - if _value.is_a?(Array) - _value.map { |v| _escaped(v) }.join('/') - else - _escaped(_value) - end - end - end - alias_method :to_s, :to_param - - private - def _escaped(value) - v = value.respond_to?(:to_param) ? value.to_param : value - _escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s - end - end end end end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index c46b39fc23..d0c66eda60 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -963,7 +963,7 @@ module ActionView end end - (field_helpers - %w(label check_box radio_button fields_for)).each do |selector| + (field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector| src = <<-end_src def #{selector}(method, options = {}) # def text_field(method, options = {}) @template.send( # @template.send( @@ -1022,6 +1022,11 @@ module ActionView def radio_button(method, tag_value, options = {}) @template.radio_button(@object_name, method, tag_value, objectify_options(options)) end + + def hidden_field(method, options = {}) + @emitted_hidden_id = true if method == :id + @template.hidden_field(@object_name, method, objectify_options(options)) + end def error_message_on(method, *args) @template.error_message_on(@object, method, *args) @@ -1035,6 +1040,10 @@ module ActionView @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit")) end + def emitted_hidden_id? + @emitted_hidden_id + end + private def objectify_options(options) @default_options.merge(options.merge(:object => @object)) @@ -1069,8 +1078,8 @@ module ActionView @template.fields_for(name, object, *args, &block) else @template.fields_for(name, object, *args) do |builder| - @template.concat builder.hidden_field(:id) block.call(builder) + @template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id? end end end diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index b070f925d4..fee9cf46f9 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -501,6 +501,12 @@ class RespondWithController < ActionController::Base respond_with(Customer.new("david", 13), :responder => responder) end + def using_resource_with_action + respond_with(Customer.new("david", 13), :action => :foo) do |format| + format.html { raise ActionView::MissingTemplate.new([], "method") } + end + end + protected def _render_js(js, options) @@ -715,6 +721,20 @@ class RespondWithControllerTest < ActionController::TestCase assert_match /<name>jamis<\/name>/, @response.body end + def test_using_resource_with_action + @controller.instance_eval do + def render(params={}) + self.response_body = "#{params[:action]} - #{formats}" + end + end + + errors = { :name => :invalid } + Customer.any_instance.stubs(:errors).returns(errors) + + post :using_resource_with_action + assert_equal "foo - #{[:html].to_s}", @controller.response_body + end + def test_clear_respond_to @controller = InheritedRespondWithController.new @request.accept = "text/html" @@ -760,7 +780,7 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "Resource name is david", @response.body end - def test_using_resource_with_responder + def test_using_resource_with_set_responder RespondWithController.responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" } get :using_resource assert_equal "Resource name is david", @response.body diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 7111796f8d..09003adf73 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -18,7 +18,7 @@ module RequestForgeryProtectionActions def unsafe render :text => 'pwn' end - + def rescue_action(e) raise e end end @@ -40,6 +40,13 @@ class FreeCookieController < RequestForgeryProtectionController end end +class CustomAuthenticityParamController < RequestForgeryProtectionController + def form_authenticity_param + 'foobar' + end +end + + # common test methods module RequestForgeryProtectionTests @@ -241,3 +248,14 @@ class FreeCookieControllerTest < ActionController::TestCase end end end + +class CustomAuthenticityParamControllerTest < ActionController::TestCase + def setup + ActionController::Base.request_forgery_protection_token = :authenticity_token + end + + def test_should_allow_custom_token + post :index, :authenticity_token => 'foobar' + assert_response :ok + end +end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 3971aacadb..4eaf309c41 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1418,6 +1418,7 @@ class RouteSetTest < ActiveSupport::TestCase :action => 'show', :requirements => {:name => /(david|jamis)/i} end + url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) assert_equal "/page/david", url assert_raise ActionController::RoutingError do @@ -1459,13 +1460,16 @@ class RouteSetTest < ActiveSupport::TestCase jamis #The Deployer )/x} end - url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) - assert_equal "/page/david", url - assert_raise ActionController::RoutingError do - url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) - end - assert_raise ActionController::RoutingError do - url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) + + pending do + url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) + assert_equal "/page/david", url + assert_raise ActionController::RoutingError do + url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) + end + assert_raise ActionController::RoutingError do + url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) + end end end @@ -1480,8 +1484,11 @@ class RouteSetTest < ActiveSupport::TestCase jamis #The Deployer )/xi} end - url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) - assert_equal "/page/JAMIS", url + + pending do + url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) + assert_equal "/page/JAMIS", url + end end def test_route_requirement_recognize_with_xi_modifiers diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index ca07bc7a28..496445fc34 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -212,9 +212,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest with_test_routes do get '/projects/1/involvements' assert_equal 'involvements#index', @response.body + assert_equal '/projects/1/involvements', project_involvements_path(:project_id => '1') get '/projects/1/involvements/1' assert_equal 'involvements#show', @response.body + assert_equal '/projects/1/involvements/1', project_involvement_path(:project_id => '1', :id => '1') end end @@ -222,6 +224,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest with_test_routes do get '/projects/1/attachments' assert_equal 'attachments#index', @response.body + assert_equal '/projects/1/attachments', project_attachments_path(:project_id => '1') end end @@ -229,9 +232,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest with_test_routes do get '/projects/1/participants' assert_equal 'participants#index', @response.body + assert_equal '/projects/1/participants', project_participants_path(:project_id => '1') put '/projects/1/participants/update_all' assert_equal 'participants#update_all', @response.body + + pending do + assert_equal '/projects/1/participants/update_all', update_all_project_participants_path(:project_id => '1') + end end end @@ -239,12 +247,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest with_test_routes do get '/projects/1/companies' assert_equal 'companies#index', @response.body + assert_equal '/projects/1/companies', project_companies_path(:project_id => '1') get '/projects/1/companies/1/people' assert_equal 'people#index', @response.body + pending do + assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1') + end get '/projects/1/companies/1/avatar' assert_equal 'avatars#show', @response.body + pending do + assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1') + end end end @@ -252,9 +267,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest with_test_routes do get '/projects/1/images' assert_equal 'images#index', @response.body + assert_equal '/projects/1/images', project_images_path(:project_id => '1') post '/projects/1/images/1/revise' assert_equal 'images#revise', @response.body + pending do + assert_equal '/projects/1/images/1/revise', revise_project_image_path(:project_id => '1', :id => '1') + end end end @@ -262,21 +281,35 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest with_test_routes do get '/projects/1/people' assert_equal 'people#index', @response.body + assert_equal '/projects/1/people', project_people_path(:project_id => '1') get '/projects/1/people/1' assert_equal 'people#show', @response.body + assert_equal '/projects/1/people/1', project_person_path(:project_id => '1', :id => '1') get '/projects/1/people/1/7a2dec8/avatar' assert_equal 'avatars#show', @response.body + pending do + assert_equal '/projects/1/people/1/7a2dec8/avatar', project_person_avatar_path(:project_id => '1', :person_id => '1', :access_token => '7a2dec8') + end put '/projects/1/people/1/accessible_projects' assert_equal 'people#accessible_projects', @response.body + pending do + assert_equal '/projects/1/people/1/accessible_projects', accessible_projects_project_person_path(:project_id => '1', :id => '1') + end post '/projects/1/people/1/resend' assert_equal 'people#resend', @response.body + pending do + assert_equal '/projects/1/people/1/resend', resend_project_person_path(:project_id => '1', :id => '1') + end post '/projects/1/people/1/generate_new_password' assert_equal 'people#generate_new_password', @response.body + pending do + assert_equal '/projects/1/people/1/generate_new_password', generate_new_password_project_person_path(:project_id => '1', :id => '1') + end end end @@ -284,24 +317,43 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest with_test_routes do get '/projects/1/posts' assert_equal 'posts#index', @response.body + assert_equal '/projects/1/posts', project_posts_path(:project_id => '1') get '/projects/1/posts/archive' assert_equal 'posts#archive', @response.body + pending do + assert_equal '/projects/1/posts/archive', archive_project_posts_path(:project_id => '1') + end get '/projects/1/posts/toggle_view' assert_equal 'posts#toggle_view', @response.body + pending do + assert_equal '/projects/1/posts/toggle_view', toggle_view_project_posts_path(:project_id => '1') + end post '/projects/1/posts/1/preview' assert_equal 'posts#preview', @response.body + pending do + assert_equal '/projects/1/posts/1/preview', preview_project_post_path(:project_id => '1', :id => '1') + end get '/projects/1/posts/1/subscription' assert_equal 'subscriptions#show', @response.body + pending do + assert_equal '/projects/1/posts/1/subscription', project_post_subscription_path(:project_id => '1', :post_id => '1') + end get '/projects/1/posts/1/comments' assert_equal 'comments#index', @response.body + pending do + assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1') + end post '/projects/1/posts/1/comments/preview' assert_equal 'comments#preview', @response.body + pending do + assert_equal '/projects/1/posts/1/comments/preview', preview_project_post_comments_path(:project_id => '1', :post_id => '1') + end end end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 04c635e770..44734abb18 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -613,6 +613,26 @@ class FormHelperTest < ActionView::TestCase expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + + '</form>' + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_existing_records_on_a_nested_attributes_one_to_one_association_with_explicit_hidden_field_placement + @post.author = Author.new(321) + + form_for(:post, @post) do |f| + concat f.text_field(:title) + f.fields_for(:author) do |af| + concat af.hidden_field(:id) + concat af.text_field(:name) + end + end + + expected = '<form action="http://www.example.com" method="post">' + + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + '</form>' @@ -634,6 +654,30 @@ class FormHelperTest < ActionView::TestCase expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + + '</form>' + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collection_association_with_explicit_hidden_field_placement + @post.comments = Array.new(2) { |id| Comment.new(id + 1) } + + form_for(:post, @post) do |f| + concat f.text_field(:title) + @post.comments.each do |comment| + f.fields_for(:comments, comment) do |cf| + concat cf.hidden_field(:id) + concat cf.text_field(:name) + end + end + end + + expected = '<form action="http://www.example.com" method="post">' + + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + @@ -678,8 +722,8 @@ class FormHelperTest < ActionView::TestCase expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + '</form>' @@ -713,10 +757,10 @@ class FormHelperTest < ActionView::TestCase expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + '</form>' assert_dom_equal expected, output_buffer @@ -736,8 +780,8 @@ class FormHelperTest < ActionView::TestCase expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + '</form>' @@ -755,8 +799,8 @@ class FormHelperTest < ActionView::TestCase end expected = '<form action="http://www.example.com" method="post">' + - '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' + '</form>' assert_dom_equal expected, output_buffer @@ -790,18 +834,18 @@ class FormHelperTest < ActionView::TestCase end expected = '<form action="http://www.example.com" method="post">' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' + - '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' + + '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" size="30" type="text" value="tag #123" />' + - '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' + '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #3141" />' + - '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' + + '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' + + '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' + '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" size="30" type="text" value="tag #456" />' + - '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' + '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #31415" />' + + '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' + + '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' + '</form>' assert_dom_equal expected, output_buffer diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index 2ad8ca9dea..42ca463f82 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/reverse_merge' + module ActiveModel module Translation include ActiveModel::Naming diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index e41fda7a4b..9f7b2a60b2 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -1,3 +1,6 @@ +require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/enumerable' + module ActiveRecord # See ActiveRecord::AssociationPreload::ClassMethods for documentation. module AssociationPreload #:nodoc: @@ -82,7 +85,7 @@ module ActiveRecord # only one level deep in the +associations+ argument, i.e. it's not passed # to the child associations when +associations+ is a Hash. def preload_associations(records, associations, preload_options={}) - records = [records].flatten.compact.uniq + records = Array.wrap(records).compact.uniq return if records.empty? case associations when Array then associations.each {|association| preload_associations(records, association, preload_options)} @@ -92,7 +95,7 @@ module ActiveRecord raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol) preload_associations(records, parent, preload_options) reflection = reflections[parent] - parents = records.map {|record| record.send(reflection.name)}.flatten.compact + parents = records.sum { |record| Array.wrap(record.send(reflection.name)) } unless parents.empty? parents.first.class.preload_associations(parents, child) end @@ -123,7 +126,8 @@ module ActiveRecord parent_records.each do |parent_record| association_proxy = parent_record.send(reflection_name) association_proxy.loaded - association_proxy.target.push(*[associated_record].flatten) + association_proxy.target.push *Array.wrap(associated_record) + association_proxy.__send__(:set_inverse_instance, associated_record, parent_record) end end @@ -254,6 +258,7 @@ module ActiveRecord through_reflection = reflections[through_association] through_primary_key = through_reflection.primary_key_name + through_records = [] if reflection.options[:source_type] interface = reflection.source_reflection.options[:foreign_type] preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]} @@ -262,23 +267,22 @@ module ActiveRecord records.first.class.preload_associations(records, through_association, preload_options) # Dont cache the association - we would only be caching a subset - through_records = [] records.each do |record| proxy = record.send(through_association) if proxy.respond_to?(:target) - through_records << proxy.target + through_records.concat Array.wrap(proxy.target) proxy.reset else # this is a has_one :through reflection through_records << proxy if proxy end end - through_records.flatten! else records.first.class.preload_associations(records, through_association) - through_records = records.map {|record| record.send(through_association)}.flatten + records.each do |record| + through_records.concat Array.wrap(record.send(through_association)) + end end - through_records.compact! through_records end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3a5f3ed030..0fcd288fc5 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/enumerable' module ActiveRecord class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc: @@ -60,6 +61,12 @@ module ActiveRecord end end + class HasAndBelongsToManyAssociationWithPrimaryKeyError < ActiveRecordError #:nodoc: + def initialize(reflection) + super("Primary key is not allowed in a has_and_belongs_to_many join table (#{reflection.options[:join_table]}).") + end + end + class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc: def initialize(reflection) super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.") @@ -1396,8 +1403,8 @@ module ActiveRecord end define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| - ids = (new_value || []).reject { |nid| nid.blank? } - send("#{reflection.name}=", reflection.klass.find(ids)) + ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i) + send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids)) end end end @@ -1674,7 +1681,6 @@ module ActiveRecord def create_has_and_belongs_to_many_reflection(association_id, options, &extension) options.assert_valid_keys(valid_keys_for_has_and_belongs_to_many_association) - options[:extend] = create_extension_modules(association_id, extension, options[:extend]) reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self) @@ -1684,6 +1690,9 @@ module ActiveRecord end reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name)) + if connection.supports_primary_key? && (connection.primary_key(reflection.options[:join_table]) rescue false) + raise HasAndBelongsToManyAssociationWithPrimaryKeyError.new(reflection) + end reflection end @@ -1922,12 +1931,16 @@ module ActiveRecord reflection = base.reflections[name] is_collection = [:has_many, :has_and_belongs_to_many].include?(reflection.macro) - parent_records = records.map do |record| - descendant = record.send(reflection.name) - next unless descendant - descendant.target.uniq! if is_collection - descendant - end.flatten.compact + parent_records = [] + records.each do |record| + if descendant = record.send(reflection.name) + if is_collection + parent_records.concat descendant.target.uniq + else + parent_records << descendant + end + end + end remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty? end diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 75218c01d2..7d8f4670fa 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -256,10 +256,16 @@ module ActiveRecord end end - # Array#flatten has problems with recursive arrays. Going one level - # deeper solves the majority of the problems. - def flatten_deeper(array) - array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten + if RUBY_VERSION < '1.9.2' + # Array#flatten has problems with recursive arrays before Ruby 1.9.2. + # Going one level deeper solves the majority of the problems. + def flatten_deeper(array) + array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten + end + else + def flatten_deeper(array) + array.flatten + end end # Returns the ID of the owner, quoted if needed. diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index c646fe488b..b01faa5212 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -1,11 +1,6 @@ module ActiveRecord module Associations class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc: - def initialize(owner, reflection) - super - @primary_key_list = {} - end - def create(attributes = {}) create_record(attributes) { |record| insert_record(record) } end @@ -23,9 +18,7 @@ module ActiveRecord end def has_primary_key? - return @has_primary_key unless @has_primary_key.nil? - @has_primary_key = (@owner.connection.supports_primary_key? && - @owner.connection.primary_key(@reflection.options[:join_table])) + @has_primary_key ||= @owner.connection.supports_primary_key? && @owner.connection.primary_key(@reflection.options[:join_table]) end protected @@ -40,11 +33,6 @@ module ActiveRecord end def insert_record(record, force = true, validate = true) - if has_primary_key? - raise ActiveRecord::ConfigurationError, - "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})." - end - if record.new_record? if force record.save! diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ab7ad34b9e..3a9a67e3a2 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -31,11 +31,10 @@ module ActiveRecord self.class.define_attribute_methods method_name = method_id.to_s guard_private_attribute_method!(method_name, args) - if self.class.generated_attribute_methods.instance_methods.include?(method_name) - return self.send(method_id, *args, &block) - end + send(method_id, *args, &block) + else + super end - super end def respond_to?(*args) diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index c8cd79a2b0..986bc7009b 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -23,16 +23,6 @@ module ActiveRecord # p2.first_name = "should fail" # p2.save # Raises a ActiveRecord::StaleObjectError # - # Optimistic locking will also check for stale data when objects are destroyed. Example: - # - # p1 = Person.find(1) - # p2 = Person.find(1) - # - # p1.first_name = "Michael" - # p1.save - # - # p2.destroy # Raises a ActiveRecord::StaleObjectError - # # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, # or otherwise apply the business logic needed to resolve the conflict. # @@ -49,7 +39,6 @@ module ActiveRecord self.lock_optimistically = true alias_method_chain :update, :lock - alias_method_chain :destroy, :lock alias_method_chain :attributes_from_column_definition, :lock class << self @@ -111,29 +100,6 @@ module ActiveRecord end end - def destroy_with_lock #:nodoc: - return destroy_without_lock unless locking_enabled? - - unless new_record? - lock_col = self.class.locking_column - previous_value = send(lock_col).to_i - - arel_table = self.class.arel_table(self.class.table_name) - - affected_rows = arel_table.where( - arel_table[self.class.primary_key].eq(quoted_id).and( - arel_table[self.class.locking_column].eq(quote_value(previous_value)) - ) - ).delete - - unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object" - end - end - - freeze - end - module ClassMethods DEFAULT_LOCKING_COLUMN = 'lock_version' diff --git a/activerecord/test/cases/associations/habtm_join_table_test.rb b/activerecord/test/cases/associations/habtm_join_table_test.rb index bf3e04c3eb..745f169ad7 100644 --- a/activerecord/test/cases/associations/habtm_join_table_test.rb +++ b/activerecord/test/cases/associations/habtm_join_table_test.rb @@ -36,21 +36,9 @@ class HabtmJoinTableTest < ActiveRecord::TestCase uses_transaction :test_should_raise_exception_when_join_table_has_a_primary_key def test_should_raise_exception_when_join_table_has_a_primary_key if ActiveRecord::Base.connection.supports_primary_key? - assert_raise ActiveRecord::ConfigurationError do - jaime = MyReader.create(:name=>"Jaime") - jaime.my_books << MyBook.create(:name=>'Great Expectations') + assert_raise ActiveRecord::HasAndBelongsToManyAssociationWithPrimaryKeyError do + MyReader.has_and_belongs_to_many :my_books end end end - - uses_transaction :test_should_cache_result_of_primary_key_check - def test_should_cache_result_of_primary_key_check - if ActiveRecord::Base.connection.supports_primary_key? - ActiveRecord::Base.connection.stubs(:primary_key).with('my_books_my_readers').returns(false).once - weaz = MyReader.create(:name=>'Weaz') - - weaz.my_books << MyBook.create(:name=>'Great Expectations') - weaz.my_books << MyBook.create(:name=>'Greater Expectations') - end - end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 5f13b66d11..fe68d03de2 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -137,6 +137,28 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert !posts(:welcome).reload.people(true).include?(people(:michael)) end + def test_replace_order_is_preserved + posts(:welcome).people.clear + posts(:welcome).people = [people(:david), people(:michael)] + assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + + # Test the inverse order in case the first success was a coincidence + posts(:welcome).people.clear + posts(:welcome).people = [people(:michael), people(:david)] + assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + end + + def test_replace_by_id_order_is_preserved + posts(:welcome).people.clear + posts(:welcome).person_ids = [people(:david).id, people(:michael).id] + assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + + # Test the inverse order in case the first success was a coincidence + posts(:welcome).people.clear + posts(:welcome).person_ids = [people(:michael).id, people(:david).id] + assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + end + def test_associate_with_create assert_queries(1) { posts(:thinking) } diff --git a/activerecord/test/cases/copy_table_test_sqlite.rb b/activerecord/test/cases/copy_table_test_sqlite.rb index de8af30997..575b4806c1 100644 --- a/activerecord/test/cases/copy_table_test_sqlite.rb +++ b/activerecord/test/cases/copy_table_test_sqlite.rb @@ -1,7 +1,7 @@ require "cases/helper" class CopyTableTest < ActiveRecord::TestCase - fixtures :companies, :comments + fixtures :customers, :companies, :comments def setup @connection = ActiveRecord::Base.connection @@ -27,8 +27,8 @@ class CopyTableTest < ActiveRecord::TestCase test_copy_table('customers', 'customers2', :rename => {'name' => 'person_name'}) do |from, to, options| expected = column_values(from, 'name') - assert expected.any?, 'only nils in resultset; real values are needed' assert_equal expected, column_values(to, 'person_name') + assert expected.any?, "No values in table: #{expected.inspect}" end end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index e177235591..a64c01292f 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -38,24 +38,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end - def test_lock_destroy - p1 = Person.find(1) - p2 = Person.find(1) - assert_equal 0, p1.lock_version - assert_equal 0, p2.lock_version - - p1.first_name = 'stu' - p1.save! - assert_equal 1, p1.lock_version - assert_equal 0, p2.lock_version - - assert_raises(ActiveRecord::StaleObjectError) { p2.destroy } - - assert p1.destroy - assert_equal true, p1.frozen? - assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) } - end - def test_lock_repeating p1 = Person.find(1) p2 = Person.find(1) @@ -282,11 +264,14 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) assert first.end > second.end end - def test_second_lock_waits - assert [0.2, 1, 5].any? { |zzz| - first, second = duel(zzz) { Person.find 1, :lock => true } - second.end > first.end - } + # Hit by ruby deadlock detection since connection checkout is mutexed. + if RUBY_VERSION < '1.9.0' + def test_second_lock_waits + assert [0.2, 1, 5].any? { |zzz| + first, second = duel(zzz) { Person.find 1, :lock => true } + second.end > first.end + } + end end protected diff --git a/activerecord/test/fixtures/edges.yml b/activerecord/test/fixtures/edges.yml index c16c70dd2f..b804f7b6a6 100644 --- a/activerecord/test/fixtures/edges.yml +++ b/activerecord/test/fixtures/edges.yml @@ -1,6 +1,5 @@ <% (1..4).each do |id| %> edge_<%= id %>: - id: <%= id %> source_id: <%= id %> sink_id: <%= id + 1 %> -<% end %>
\ No newline at end of file +<% end %> diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 15e5e12d03..0dd9da4c11 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -160,7 +160,7 @@ ActiveRecord::Schema.define do t.integer :access_level, :default => 1 end - create_table :edges, :force => true do |t| + create_table :edges, :force => true, :id => false do |t| t.column :source_id, :integer, :null => false t.column :sink_id, :integer, :null => false end diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index bec9de86ed..1b6b820ca4 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -38,6 +38,11 @@ module ActiveSupport # # If no addresses are specified, then MemCacheStore will connect to # localhost port 11211 (the default memcached port). + # + # Instead of addresses one can pass in a MemCache-like object. For example: + # + # require 'memcached' # gem install memcached; uses C bindings to libmemcached + # ActiveSupport::Cache::MemCacheStore.new(Memcached::Rails.new("localhost:11211")) def initialize(*addresses) if addresses.first.respond_to?(:get) @data = addresses.first diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index 38bb68c1ec..f48d5ce500 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -4,11 +4,10 @@ class Array def self.wrap(object) if object.nil? [] - # to_a doesn't work correctly with Array() but to_ary always does - elsif object.respond_to?(:to_a) && !object.respond_to?(:to_ary) - [object] + elsif object.respond_to?(:to_ary) + object.to_ary else - Array(object) + [object] end end end diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb deleted file mode 100644 index 6672129076..0000000000 --- a/activesupport/lib/active_support/core_ext/date.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'date' - -require 'active_support/core_ext/date/acts_like' -require 'active_support/core_ext/date/freeze' - -require 'active_support/core_ext/date/calculations' -require 'active_support/core_ext/date/conversions' diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index b41ad5b686..f6c870035b 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,7 +1,4 @@ require 'active_support/inflector' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/date_time/conversions' -require 'active_support/values/time_zone' class Date DATE_FORMATS = { diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb deleted file mode 100644 index 004fd0ad29..0000000000 --- a/activesupport/lib/active_support/core_ext/date_time.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/core_ext/time' -require 'active_support/core_ext/date_time/acts_like' -require 'active_support/core_ext/date_time/calculations' -require 'active_support/core_ext/date_time/conversions' -require 'active_support/core_ext/date_time/zones' diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb index 22749229a3..e4df8fe338 100644 --- a/activesupport/lib/active_support/core_ext/logger.rb +++ b/activesupport/lib/active_support/core_ext/logger.rb @@ -137,10 +137,10 @@ class Logger attr_writer :formatter public :formatter= - alias old_format_datetime format_datetime + alias old_format_datetime format_datetime if method_defined?(:format_datetime) def format_datetime(datetime) datetime end - alias old_msg2str msg2str + alias old_msg2str msg2str if method_defined?(:msg2str) def msg2str(msg) msg end end end diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb index cd165626c8..e1ebd4f91c 100644 --- a/activesupport/lib/active_support/core_ext/name_error.rb +++ b/activesupport/lib/active_support/core_ext/name_error.rb @@ -1,7 +1,9 @@ class NameError # Extract the name of the missing constant from the exception message. def missing_name - $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message + if /undefined local variable or method/ !~ message + $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message + end end # Was this exception raised because the given name was missing? diff --git a/activesupport/lib/active_support/core_ext/string/interpolation.rb b/activesupport/lib/active_support/core_ext/string/interpolation.rb index 41a061c1a4..2048d35091 100644 --- a/activesupport/lib/active_support/core_ext/string/interpolation.rb +++ b/activesupport/lib/active_support/core_ext/string/interpolation.rb @@ -5,7 +5,7 @@ You may redistribute it and/or modify it under the same license terms as Ruby. =end -if RUBY_VERSION < '1.9' +if RUBY_VERSION < '1.9' && !"".respond_to?(:interpolate_without_ruby_19_syntax) # KeyError is raised by String#% when the string contains a named placeholder # that is not contained in the given arguments hash. Ruby 1.9 includes and diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb deleted file mode 100644 index b28f7f1a32..0000000000 --- a/activesupport/lib/active_support/core_ext/time.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'date' -require 'time' - -require 'active_support/core_ext/time/publicize_conversion_methods' -require 'active_support/core_ext/time/marshal_with_utc_flag' - -require 'active_support/core_ext/time/acts_like' -require 'active_support/core_ext/time/calculations' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/time/zones' diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 8e8f9022c1..3c15056c41 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -6,7 +6,7 @@ require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/object/instance_variables' require 'active_support/deprecation' -require 'active_support/time_with_zone' +require 'active_support/time' # Hack to load json gem first so we can overwrite its to_json. begin diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 6304f496f5..e2540cd598 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -41,7 +41,7 @@ module ActiveSupport # to subscribers in a thread. You can use any queue implementation you want. # module Notifications - mattr_accessor :queue + mattr_accessor :queue, :listener class << self delegate :instrument, :transaction_id, :transaction, :to => :instrumenter @@ -54,8 +54,13 @@ module ActiveSupport @publisher ||= Publisher.new(queue) end - def subscribe(pattern=nil, &block) - Subscriber.new(queue).bind(pattern).subscribe(&block) + def subscriber + @subscriber ||= Subscriber.new(queue) + end + + def subscribe(pattern=nil, options={}, &block) + with = options[:with] || listener + subscriber.bind(with, pattern).subscribe(&block) end end @@ -104,13 +109,14 @@ module ActiveSupport @queue = queue end - def bind(pattern) - @pattern = pattern + def bind(listener, pattern) + @listener = listener + @pattern = pattern self end def subscribe - @queue.subscribe(@pattern) do |*args| + @queue.subscribe(@listener, @pattern) do |*args| yield(*args) end end @@ -138,54 +144,70 @@ module ActiveSupport end end - # This is a default queue implementation that ships with Notifications. It - # consumes events in a thread and publish them to all registered subscribers. - # - class LittleFanout - def initialize - @listeners = [] - @stream = Queue.new + class AsyncListener + def initialize(pattern, &block) + @pattern = pattern + @subscriber = block + @queue = Queue.new Thread.new { consume } end - def publish(*args) - @stream.push(args) - end - - def subscribe(pattern=nil, &block) - @listeners << Listener.new(pattern, &block) + def publish(name, *args) + if !@pattern || @pattern === name.to_s + @queue << args.unshift(name) + end end def consume - while args = @stream.shift - @listeners.each { |l| l.publish(*args) } + while args = @queue.shift + @subscriber.call(*args) end end - class Listener - # attr_reader :thread + def drained? + @queue.size.zero? + end + end - def initialize(pattern, &block) - @pattern = pattern - @subscriber = block - @queue = Queue.new - Thread.new { consume } - end + class SyncListener + def initialize(pattern, &block) + @pattern = pattern + @subscriber = block + end - def publish(name, *args) - if !@pattern || @pattern === name.to_s - @queue << args.unshift(name) - end + def publish(name, *args) + if !@pattern || @pattern === name.to_s + @subscriber.call(*args.unshift(name)) end + end - def consume - while args = @queue.shift - @subscriber.call(*args) - end - end + def drained? + true + end + end + + # This is a default queue implementation that ships with Notifications. It + # consumes events in a thread and publish them to all registered subscribers. + # + class LittleFanout + def initialize + @listeners = [] + end + + def publish(*args) + @listeners.each { |l| l.publish(*args) } + end + + def subscribe(listener, pattern=nil, &block) + @listeners << listener.new(pattern, &block) + end + + def drained? + @listeners.all? &:drained? end end end - Notifications.queue = Notifications::LittleFanout.new + Notifications.queue = Notifications::LittleFanout.new + Notifications.listener = Notifications::AsyncListener end diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index 879662c16c..f0119f5994 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -81,7 +81,7 @@ module ActiveSupport def handler_for_rescue(exception) # We go from right to left because pairs are pushed onto rescue_handlers # as rescue_from declarations are found. - _, rescuer = Array(rescue_handlers).reverse.detect do |klass_name, handler| + _, rescuer = rescue_handlers.reverse.detect do |klass_name, handler| # The purpose of allowing strings in rescue_from is to support the # declaration of handler associations for exception classes whose # definition is yet unknown. diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index c75b59c284..81f7e3c9df 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,10 +1,25 @@ module ActiveSupport module Testing + class RemoteError < StandardError + + attr_reader :message, :backtrace + + def initialize(exception) + @message = "caught #{exception.class.name}: #{exception.message}" + @backtrace = exception.backtrace + end + end + class ProxyTestResult def initialize @calls = [] end + def add_error(e) + e = Test::Unit::Error.new(e.test_name, RemoteError.new(e.exception)) + @calls << [:add_error, e] + end + def __replay__(result) @calls.each do |name, args| result.send(name, *args) diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb index d36a683601..0f421421d0 100644 --- a/activesupport/lib/active_support/time.rb +++ b/activesupport/lib/active_support/time.rb @@ -1,7 +1,4 @@ require 'active_support' -require 'active_support/core_ext/time' -require 'active_support/core_ext/date' -require 'active_support/core_ext/date_time' module ActiveSupport autoload :Duration, 'active_support/duration' @@ -12,3 +9,26 @@ module ActiveSupport [Duration, TimeWithZone, TimeZone] end end + +require 'date' +require 'time' + +require 'active_support/core_ext/time/publicize_conversion_methods' +require 'active_support/core_ext/time/marshal_with_utc_flag' +require 'active_support/core_ext/time/acts_like' +require 'active_support/core_ext/time/calculations' +require 'active_support/core_ext/time/conversions' +require 'active_support/core_ext/time/zones' + +require 'active_support/core_ext/date/acts_like' +require 'active_support/core_ext/date/freeze' +require 'active_support/core_ext/date/calculations' +require 'active_support/core_ext/date/conversions' + +require 'active_support/core_ext/date_time/acts_like' +require 'active_support/core_ext/date_time/calculations' +require 'active_support/core_ext/date_time/conversions' +require 'active_support/core_ext/date_time/zones' + +require 'active_support/core_ext/integer/time' +require 'active_support/core_ext/numeric/time' diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index a64128e49e..8304f6c434 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -1,10 +1,4 @@ -require 'active_support/duration' -require 'active_support/values/time_zone' -require 'active_support/core_ext/numeric/time' -require 'active_support/core_ext/integer/time' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/date/conversions' -require 'active_support/core_ext/date_time/conversions' +require "active_support/values/time_zone" module ActiveSupport # A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index e7583bef1b..cbb8e890ae 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -1,7 +1,4 @@ require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/time' -require 'active_support/core_ext/date' -require 'active_support/core_ext/date_time' # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. It allows us to do the following: # diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb index 4a711b7e25..eb5080888c 100644 --- a/activesupport/lib/active_support/vendor.rb +++ b/activesupport/lib/active_support/vendor.rb @@ -9,7 +9,7 @@ end unless ActiveSupport.requirable? lib # Try to activate a gem ~> satisfying the requested version first. begin - gem lib, "~> #{version}" + gem lib, ">= #{version}" # Use the vendored lib if the gem's missing or we aren't using RubyGems. rescue LoadError, NoMethodError # There could be symlinks diff --git a/activesupport/lib/active_support/whiny_nil.rb b/activesupport/lib/active_support/whiny_nil.rb index 36fe9510ba..c4aaba7ab3 100644 --- a/activesupport/lib/active_support/whiny_nil.rb +++ b/activesupport/lib/active_support/whiny_nil.rb @@ -43,7 +43,14 @@ class NilClass private def method_missing(method, *args, &block) - raise_nil_warning_for METHOD_CLASS_MAP[method], method, caller + # Ruby 1.9.2: disallow explicit coercion via method_missing. + if method == :to_ary || method == :to_str + super + elsif klass = METHOD_CLASS_MAP[method] + raise_nil_warning_for klass, method, caller + else + super + end end # Raises a NoMethodError when you attempt to call a method on +nil+. @@ -55,4 +62,3 @@ class NilClass raise NoMethodError, message, with_caller || caller end end - diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 6a51ce9402..00e05f76fe 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -30,7 +30,9 @@ class CacheStoreSettingTest < ActiveSupport::TestCase def test_mem_cache_fragment_cache_store_with_given_mem_cache_like_object MemCache.expects(:new).never - store = ActiveSupport::Cache.lookup_store :mem_cache_store, stub("memcache", :get => true) + memcache = Object.new + def memcache.get() true end + store = ActiveSupport::Cache.lookup_store :mem_cache_store, memcache assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 8198b9bd2c..f5f91ddd80 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -339,6 +339,11 @@ class ArrayWrapperTests < Test::Unit::TestCase end end + class Proxy + def initialize(target) @target = target end + def method_missing(*a) @target.send(*a) end + end + def test_array ary = %w(foo bar) assert_same ary, Array.wrap(ary) @@ -364,4 +369,19 @@ class ArrayWrapperTests < Test::Unit::TestCase def test_object_with_to_ary assert_equal ["foo", "bar"], Array.wrap(FakeCollection.new) end + + def test_proxy_object + p = Proxy.new(Object.new) + assert_equal [p], Array.wrap(p) + end + + def test_proxy_to_object_with_to_ary + p = Proxy.new(FakeCollection.new) + assert_equal [p], Array.wrap(p) + end + + def test_struct + o = Struct.new(:foo).new(123) + assert_equal [o], Array.wrap(o) + end end diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 18422d68bc..23c9bc7fb1 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -1,5 +1,5 @@ require 'abstract_unit' -require 'active_support/core_ext/date' +require 'active_support/time' class DateExtCalculationsTest < Test::Unit::TestCase def test_to_s diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index a7b179b2be..4341ead488 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -1,5 +1,5 @@ require 'abstract_unit' -require 'active_support/core_ext/date_time' +require 'active_support/time' class DateTimeExtCalculationsTest < Test::Unit::TestCase def test_to_s diff --git a/activesupport/test/core_ext/name_error_test.rb b/activesupport/test/core_ext/name_error_test.rb index 10913e2ade..6352484d04 100644 --- a/activesupport/test/core_ext/name_error_test.rb +++ b/activesupport/test/core_ext/name_error_test.rb @@ -3,23 +3,19 @@ require 'active_support/core_ext/name_error' class NameErrorTest < Test::Unit::TestCase def test_name_error_should_set_missing_name - begin - SomeNameThatNobodyWillUse____Really ? 1 : 0 - flunk "?!?!" - rescue NameError => exc - assert_equal "NameErrorTest::SomeNameThatNobodyWillUse____Really", exc.missing_name - assert exc.missing_name?(:SomeNameThatNobodyWillUse____Really) - assert exc.missing_name?("NameErrorTest::SomeNameThatNobodyWillUse____Really") - end + SomeNameThatNobodyWillUse____Really ? 1 : 0 + flunk "?!?!" + rescue NameError => exc + assert_equal "NameErrorTest::SomeNameThatNobodyWillUse____Really", exc.missing_name + assert exc.missing_name?(:SomeNameThatNobodyWillUse____Really) + assert exc.missing_name?("NameErrorTest::SomeNameThatNobodyWillUse____Really") end def test_missing_method_should_ignore_missing_name - begin - some_method_that_does_not_exist - flunk "?!?!" - rescue NameError => exc - assert_equal nil, exc.missing_name - assert ! exc.missing_name?(:Foo) - end + some_method_that_does_not_exist + flunk "?!?!" + rescue NameError => exc + assert !exc.missing_name?(:Foo) + assert_nil exc.missing_name end end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index 2565c56b8a..5701eeef28 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' +require 'active_support/time' require 'active_support/core_ext/range' -require 'active_support/core_ext/date/conversions' class RangeTest < Test::Unit::TestCase def test_to_s_from_dates diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 1e8a1c4f08..56ed296dac 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -4,7 +4,7 @@ require 'abstract_unit' require 'inflector_test_cases' require 'active_support/core_ext/string' -require 'active_support/core_ext/time' +require 'active_support/time' require 'active_support/core_ext/kernel/reporting' class StringInflectionsTest < Test::Unit::TestCase diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb index 9868f1e87d..dfcd4f822d 100644 --- a/activesupport/test/i18n_test.rb +++ b/activesupport/test/i18n_test.rb @@ -9,8 +9,8 @@ class I18nTest < Test::Unit::TestCase end def test_time_zone_localization_with_default_format - Time.zone.stubs(:now).returns Time.local(2000) - assert_equal Time.zone.now.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(Time.zone.now) + now = Time.local(2000) + assert_equal now.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(now) end def test_date_localization_should_use_default_format diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 7b5a4d0416..8fcb16abfb 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -1,6 +1,7 @@ # encoding: UTF-8 require 'abstract_unit' require 'active_support/json' +require 'active_support/time' require 'active_support/core_ext/kernel/reporting' class TestJSONDecoding < ActiveSupport::TestCase diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index ed3461571a..5c2b44f188 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'active_support/time' class MessageEncryptorTest < Test::Unit::TestCase def setup diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index ef300e4e26..714a3b3a39 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -7,6 +7,8 @@ rescue LoadError, NameError $stderr.puts "Skipping MessageVerifier test: broken OpenSSL install" else +require 'active_support/time' + class MessageVerifierTest < Test::Unit::TestCase def setup @verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!") diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 9175c8f26e..35d44367cf 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -75,7 +75,7 @@ class NotificationsMainTest < Test::Unit::TestCase 1 + 1 end - sleep(0.1) + drain assert_equal 1, @events.size assert_equal :awesome, @events.last.name @@ -88,19 +88,18 @@ class NotificationsMainTest < Test::Unit::TestCase 1 + 1 end - sleep(0.1) + drain assert_equal 1, @events.size assert_equal :wot, @events.first.name assert_equal Hash[:payload => "child"], @events.first.payload end - sleep(0.1) + drain assert_equal 2, @events.size assert_equal :awesome, @events.last.name assert_equal Hash[:payload => "notifications"], @events.last.payload - assert_in_delta 100, @events.last.duration, 70 end def test_event_is_pushed_even_if_block_fails @@ -108,7 +107,7 @@ class NotificationsMainTest < Test::Unit::TestCase raise "OMG" end rescue RuntimeError - sleep(0.1) + drain assert_equal 1, @events.size assert_equal :awesome, @events.last.name @@ -117,7 +116,7 @@ class NotificationsMainTest < Test::Unit::TestCase def test_event_is_pushed_even_without_block ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") - sleep(0.1) + drain assert_equal 1, @events.size assert_equal :awesome, @events.last.name @@ -137,8 +136,9 @@ class NotificationsMainTest < Test::Unit::TestCase end ActiveSupport::Notifications.instrument(:cache){ 1 } - sleep 0.1 + drain + assert_equal 3, @another.size before, during, after = @another.map {|e| e.transaction_id } assert_equal before, after assert_not_equal before, during @@ -153,7 +153,7 @@ class NotificationsMainTest < Test::Unit::TestCase ActiveSupport::Notifications.instrument(:cache){ 1 } - sleep(0.1) + drain assert_equal 1, @another.size assert_equal :cache, @another.first.name @@ -169,7 +169,22 @@ class NotificationsMainTest < Test::Unit::TestCase ActiveSupport::Notifications.instrument(:something){ 0 } ActiveSupport::Notifications.instrument(:cache){ 1 } - sleep(0.1) + drain + + assert_equal 1, @another.size + assert_equal :cache, @another.first.name + assert_equal 1, @another.first.result + end + + def test_subscriber_allows_sync_listeners + @another = [] + ActiveSupport::Notifications.subscribe(/cache/, :with => ActiveSupport::Notifications::SyncListener) do |*args| + @another << ActiveSupport::Notifications::Event.new(*args) + end + + Thread.expects(:new).never + ActiveSupport::Notifications.instrument(:something){ 0 } + ActiveSupport::Notifications.instrument(:cache){ 1 } assert_equal 1, @another.size assert_equal :cache, @another.first.name @@ -186,7 +201,7 @@ class NotificationsMainTest < Test::Unit::TestCase ActiveSupport::Notifications.instrument(:value){ i } end - sleep 0.1 + drain assert_equal 100, @events.size assert_equal :value, @events.first.name @@ -198,4 +213,9 @@ class NotificationsMainTest < Test::Unit::TestCase assert_equal 1, @another.first.result assert_equal 100, @another.last.result end + + private + def drain + sleep(0.05) until ActiveSupport::Notifications.queue.drained? + end end diff --git a/activesupport/test/whiny_nil_test.rb b/activesupport/test/whiny_nil_test.rb index 4cb22c41b2..009d97940f 100644 --- a/activesupport/test/whiny_nil_test.rb +++ b/activesupport/test/whiny_nil_test.rb @@ -13,7 +13,7 @@ class WhinyNilTest < Test::Unit::TestCase def test_unchanged nil.method_thats_not_in_whiners rescue NoMethodError => nme - assert_match(/nil.method_thats_not_in_whiners/, nme.message) + assert(nme.message =~ /nil:NilClass/) end def test_active_record @@ -35,4 +35,16 @@ class WhinyNilTest < Test::Unit::TestCase rescue RuntimeError => nme assert(!(nme.message =~ /nil:NilClass/)) end + + def test_no_to_ary_coercion + nil.to_ary + rescue NoMethodError => nme + assert(nme.message =~ /nil:NilClass/) + end + + def test_no_to_str_coercion + nil.to_str + rescue NoMethodError => nme + assert(nme.message =~ /nil:NilClass/) + end end diff --git a/arel b/arel new file mode 160000 +Subproject 0faeb5047407348533db952d9cf93ea59d2526d diff --git a/rack-mount b/rack-mount new file mode 160000 +Subproject 3784e633b42f43a4131e02519be60080d179da2 diff --git a/railties/CHANGELOG b/railties/CHANGELOG index d6311f77a0..66e0d5e9c5 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Fixed that the debugger wouldn't go into IRB mode because of left-over ARGVs [DHH] + * I18n support for plugins. #2325 [Antonio Tapiador, Sven Fuchs] * Ruby 1.9: use UTF-8 for default internal and external encodings. [Jeremy Kemper] diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index 43ece14a49..c23b67e321 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -1 +1,13 @@ +require "pathname" + +require 'rails/initializable' +require 'rails/application' +require 'rails/railties_path' +require 'rails/version' +require 'rails/rack' +require 'rails/paths' +require 'rails/core' +require 'rails/configuration' +require 'rails/deprecation' require 'rails/initializer' +require 'rails/plugin'
\ No newline at end of file diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index b43dcb0041..7c2d8eab67 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -3,6 +3,11 @@ module Rails include Initializable class << self + def inherited(klass) + Rails.application ||= klass unless klass.name =~ /Rails/ + super + end + # Stub out App initialize def initialize! new @@ -23,10 +28,6 @@ module Rails @config = config end - def plugin_loader - @plugin_loader ||= config.plugin_loader.new(self) - end - def root config.root end @@ -37,7 +38,7 @@ module Rails end def initialize - run_initializers + run_initializers(self) end def config @@ -46,10 +47,6 @@ module Rails alias configuration config - def plugin_loader - self.class.plugin_loader - end - def middleware config.middleware end @@ -58,6 +55,18 @@ module Rails ActionController::Routing::Routes end + def initializers + initializers = super + plugins.each { |p| initializers += p.initializers } + initializers + end + + def plugins + @plugins ||= begin + Plugin::Vendored.all(config.plugins || [:all], config.paths.vendor.plugins) + end + end + def call(env) @app ||= middleware.build(routes) @app.call(env) @@ -111,13 +120,6 @@ module Rails config.load_once_paths.freeze end - # Adds all load paths from plugins to the global set of load paths, so that - # code from plugins can be required (explicitly or automatically via ActiveSupport::Dependencies). - initializer :add_plugin_load_paths do - require 'active_support/dependencies' - plugin_loader.add_plugin_load_paths - end - # Create tmp directories initializer :ensure_tmp_directories_exist do %w(cache pids sessions sockets).each do |dir_to_make| @@ -321,7 +323,6 @@ module Rails # TODO: Make Rails and metal work without ActionController if config.frameworks.include?(:action_controller) Rails::Rack::Metal.requested_metals = config.metals - Rails::Rack::Metal.metal_paths += plugin_loader.engine_metal_paths config.middleware.insert_before( :"ActionDispatch::ParamsParser", @@ -329,26 +330,6 @@ module Rails end end - # Loads all plugins in <tt>config.plugin_paths</tt>. <tt>plugin_paths</tt> - # defaults to <tt>vendor/plugins</tt> but may also be set to a list of - # paths, such as - # config.plugin_paths = ["#{config.root}/lib/plugins", "#{config.root}/vendor/plugins"] - # - # In the default implementation, as each plugin discovered in <tt>plugin_paths</tt> is initialized: - # * its +lib+ directory, if present, is added to the load path (immediately after the applications lib directory) - # * <tt>init.rb</tt> is evaluated, if present - # - # After all plugins are loaded, duplicates are removed from the load path. - # If an array of plugin names is specified in config.plugins, only those plugins will be loaded - # and they plugins will be loaded in that order. Otherwise, plugins are loaded in alphabetical - # order. - # - # if config.plugins ends contains :all then the named plugins will be loaded in the given order and all other - # plugins will be loaded in alphabetical order - initializer :load_plugins do - plugin_loader.load_plugins - end - # # bail out if gems are missing - note that check_gem_dependencies will have # # already called abort() unless $gems_rake_task is set # return unless gems_dependencies_loaded diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 29359e49a4..2c90851fb2 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -1,4 +1,4 @@ -require 'action_controller' +require 'action_dispatch' require 'fileutils' require 'optparse' @@ -42,7 +42,7 @@ unless server end puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" -puts "=> Rails #{Rails.version} application starting on http://#{options[:Host]}:#{options[:Port]}}" +puts "=> Rails #{Rails.version} application starting on http://#{options[:Host]}:#{options[:Port]}" if options[:detach] Process.daemon diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 2b847ba711..102a0836dc 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -1,17 +1,13 @@ -require 'rails/plugin/loader' -require 'rails/plugin/locator' require 'active_support/ordered_options' module Rails class Configuration - attr_accessor :cache_classes, :load_paths, - :load_once_paths, :after_initialize_blocks, - :frameworks, :framework_root_path, :root, :plugin_paths, :plugins, - :plugin_loader, :plugin_locators, :gems, :loaded_plugins, :reload_plugins, + attr_accessor :cache_classes, :load_paths, :load_once_paths, :after_initialize_blocks, + :frameworks, :framework_root_path, :root, :gems, :plugins, :i18n, :gems, :whiny_nils, :consider_all_requests_local, :action_controller, :active_record, :action_view, :active_support, :action_mailer, :active_resource, - :log_path, :log_level, :logger, :preload_frameworks, + :reload_plugins, :log_path, :log_level, :logger, :preload_frameworks, :database_configuration_file, :cache_store, :time_zone, :view_path, :metals, :controller_paths, :routes_configuration_file, :eager_load_paths, :dependency_loading, :paths, :serve_static_assets @@ -19,7 +15,6 @@ module Rails def initialize @load_once_paths = [] @after_initialize_blocks = [] - @loaded_plugins = [] @dependency_loading = true @serve_static_assets = true @@ -199,24 +194,6 @@ module Rails @frameworks ||= [ :active_record, :action_controller, :action_view, :action_mailer, :active_resource ] end - def plugin_paths - @plugin_paths ||= ["#{root}/vendor/plugins"] - end - - def plugin_loader - @plugin_loader ||= begin - Plugin::Loader - end - end - - def plugin_locators - @plugin_locators ||= begin - locators = [] - locators << Plugin::GemLocator if defined? Gem - locators << Plugin::FileSystemLocator - end - end - def i18n @i18n ||= begin i18n = ActiveSupport::OrderedOptions.new @@ -235,10 +212,6 @@ module Rails "#{root}/config/environments/#{RAILS_ENV}.rb" end - def reload_plugins? - @reload_plugins - end - # Holds generators configuration: # # config.generators do |g| diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 19412c259e..85c2fd52a4 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -86,28 +86,16 @@ module Rails @options ||= DEFAULT_OPTIONS.dup end - # We have two scenarios here: when rubygems is loaded and when bundler is - # being used. If rubygems is loaded, we get all generators paths from loaded - # specs. Otherwise we just have to look into vendor/gems/gems. - # - def self.gems_generators_paths - paths = [] - - if defined?(Gem) && Gem.respond_to?(:loaded_specs) - Gem.loaded_specs.each do |name, spec| - generator_path = File.join(spec.full_gem_path, "lib/generators") - paths << generator_path if File.exist?(generator_path) - end + def self.gems_generators_paths #:nodoc: + return [] unless defined?(Gem) && Gem.respond_to?(:loaded_specs) + Gem.loaded_specs.inject([]) do |paths, (name, spec)| + paths += Dir[File.join(spec.full_gem_path, "lib/{generators,rails_generators}")] end - - paths end - # Load paths from plugin. - # - def self.plugins_generators_paths + def self.plugins_generators_paths #:nodoc: return [] unless Rails.root - Dir[File.join(Rails.root, "vendor", "plugins", "*", "lib", "generators")] + Dir[File.join(Rails.root, "vendor", "plugins", "*", "lib", "{generators,rails_generators}")] end # Hold configured generators fallbacks. If a plugin developer wants a @@ -147,8 +135,8 @@ module Rails def self.load_paths @load_paths ||= begin paths = [] - paths << File.join(Rails.root, "lib", "generators") if Rails.root - paths << File.join(Thor::Util.user_home, ".rails", "generators") + paths += Dir[File.join(Rails.root, "lib", "{generators,rails_generators}")] if Rails.root + paths += Dir[File.join(Thor::Util.user_home, ".rails", "{generators,rails_generators}")] paths += self.plugins_generators_paths paths += self.gems_generators_paths paths << File.expand_path(File.join(File.dirname(__FILE__), "generators")) @@ -210,7 +198,7 @@ module Rails return klass if klass end - invoke_fallbacks_for(name, base) + invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) end # Receives a namespace, arguments and the behavior to invoke the generator. @@ -278,13 +266,13 @@ module Rails # By default, Rails strips the generator namespace to make invocations # easier. This method generaters the both possibilities names. - def self.generator_names(first, second) + def self.generator_names(first, second) #:nodoc: [ "#{first}:generators:#{second}", "#{first}:#{second}" ] end # Try callbacks for the given base. # - def self.invoke_fallbacks_for(name, base) + def self.invoke_fallbacks_for(name, base) #:nodoc: return nil unless base && fallbacks[base.to_sym] invoked_fallbacks = [] diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb index d78eab9a75..3866b856b2 100644 --- a/railties/lib/rails/initializable.rb +++ b/railties/lib/rails/initializable.rb @@ -42,7 +42,8 @@ module Rails if initializer.before index = index_for(initializer.before) elsif initializer.after - index = index_for(initializer.after) + 1 + index = index_for(initializer.after) + index += 1 if index else index = length end diff --git a/railties/lib/rails/initializer.rb b/railties/lib/rails/initializer.rb index 2ad1e52746..44d04688c8 100644 --- a/railties/lib/rails/initializer.rb +++ b/railties/lib/rails/initializer.rb @@ -1,14 +1,4 @@ -require "pathname" - -require 'rails/initializable' -require 'rails/application' -require 'rails/railties_path' -require 'rails/version' -require 'rails/rack' -require 'rails/paths' -require 'rails/core' -require 'rails/configuration' -require 'rails/deprecation' +require "rails" # In case people require this file directly RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV) @@ -24,4 +14,4 @@ module Rails end end end -end +end
\ No newline at end of file diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 0f24106353..b3d105d8c7 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -64,7 +64,7 @@ module Rails end class Path - include PathParent + include PathParent, Enumerable attr_reader :path attr_accessor :glob @@ -83,6 +83,10 @@ module Rails @root.all_paths << self end + def each + to_a.each { |p| yield p } + end + def push(path) @paths.push path end diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index 090ec6e4cb..86bf032641 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -1,183 +1,64 @@ module Rails - # The Plugin class should be an object which provides the following methods: - # - # * +name+ - Used during initialisation to order the plugin (based on name and - # the contents of <tt>config.plugins</tt>). - # * +valid?+ - Returns true if this plugin can be loaded. - # * +load_paths+ - Each path within the returned array will be added to the <tt>$LOAD_PATH</tt>. - # * +load+ - Finally 'load' the plugin. - # - # These methods are expected by the Rails::Plugin::Locator and Rails::Plugin::Loader classes. - # The default implementation returns the <tt>lib</tt> directory as its <tt>load_paths</tt>, - # and evaluates <tt>init.rb</tt> when <tt>load</tt> is called. - # - # You can also inspect the about.yml data programmatically: - # - # plugin = Rails::Plugin.new(path_to_my_plugin) - # plugin.about["author"] # => "James Adam" - # plugin.about["url"] # => "http://interblah.net" class Plugin - include Comparable include Initializable - - attr_reader :directory, :name - - def initialize(directory) - @directory = directory - @name = File.basename(@directory) rescue nil - @loaded = false - end - - def valid? - File.directory?(directory) && (has_app_directory? || has_lib_directory? || has_init_file?) - end - - # Returns a list of paths this plugin wishes to make available in <tt>$LOAD_PATH</tt>. - def load_paths - report_nonexistant_or_empty_plugin! unless valid? - - load_paths = [] - load_paths << lib_path if has_lib_directory? - load_paths << app_paths if has_app_directory? - load_paths.flatten - end - - # Evaluates a plugin's init.rb file. - def load(initializer) - return if loaded? - report_nonexistant_or_empty_plugin! unless valid? - evaluate_init_rb(initializer) - @loaded = true - end - - def loaded? - @loaded - end - - def <=>(other_plugin) - name <=> other_plugin.name - end - - def about - @about ||= load_about_information - end - - # Engines are plugins with an app/ directory. - def engine? - has_app_directory? - end - - # Returns true if the engine ships with a routing file - def routed? - File.exist?(routing_file) - end - - # Returns true if there is any localization file in locale_path - def localized? - locale_files.any? - end - - def view_path - File.join(directory, 'app', 'views') - end - - def controller_path - File.join(directory, 'app', 'controllers') - end - - def metal_path - File.join(directory, 'app', 'metal') - end - - def routing_file - File.join(directory, 'config', 'routes.rb') - end - - def locale_path - File.join(directory, 'config', 'locales') - end - - def locale_files - Dir[ File.join(locale_path, '*.{rb,yml}') ] - end - - private - def load_about_information - about_yml_path = File.join(@directory, "about.yml") - parsed_yml = File.exist?(about_yml_path) ? YAML.load(File.read(about_yml_path)) : {} - parsed_yml || {} - rescue Exception - {} - end - def report_nonexistant_or_empty_plugin! - raise LoadError, "Can not find the plugin named: #{name}" - end - - def app_paths - [ File.join(directory, 'app', 'models'), File.join(directory, 'app', 'helpers'), controller_path, metal_path ] - end - - def lib_path - File.join(directory, 'lib') - end - - def classic_init_path - File.join(directory, 'init.rb') - end + class Vendored < Plugin + def self.all(list, paths) + plugins = [] + paths.each do |path| + Dir["#{path}/*"].each do |plugin_path| + plugin = new(plugin_path) + next unless list.include?(plugin.name) || list.include?(:all) + plugins << plugin + end + end - def gem_init_path - File.join(directory, 'rails', 'init.rb') - end + plugins.sort_by do |p| + [list.index(p.name) || list.index(:all), p.name.to_s] + end + end - def init_path - File.file?(gem_init_path) ? gem_init_path : classic_init_path - end + attr_reader :name, :path - def has_app_directory? - File.directory?(File.join(directory, 'app')) - end + def initialize(path) + @name = File.basename(path).to_sym + @path = path + end - def has_lib_directory? - File.directory?(lib_path) - end + def load_paths + Dir["#{path}/{lib}", "#{path}/app/{models,controllers,helpers}"] + end - def has_init_file? - File.file?(init_path) - end + initializer :add_to_load_path, :after => :set_autoload_paths do |app| + load_paths.each do |path| + $LOAD_PATH << path + require "active_support/dependencies" - def evaluate_init_rb(initializer) - if has_init_file? - require 'active_support/core_ext/kernel/reporting' - silence_warnings do - # Allow plugins to reference the current configuration object - config = initializer.configuration + ActiveSupport::Dependencies.load_paths << path - eval(IO.read(init_path), binding, init_path) + unless app.config.reload_plugins + ActiveSupport::Dependencies.load_once_paths << path + end end end - end - class Vendored < Plugin - initializer :init_rb do |application| - evaluate_init_rb(application) + initializer :load_init_rb, :before => :load_application_initializers do |app| + file = "#{@path}/init.rb" + config = app.config + eval File.read(file), binding, file if File.file?(file) end - end - end - # This Plugin subclass represents a Gem plugin. Although RubyGems has already - # taken care of $LOAD_PATHs, it exposes its load_paths to add them - # to Dependencies.load_paths. - class GemPlugin < Plugin - # Initialize this plugin from a Gem::Specification. - def initialize(spec, gem) - directory = spec.full_gem_path - super(directory) - @name = spec.name - end + initializer :add_view_paths, :after => :initialize_framework_views do + ActionController::Base.view_paths.concat ["#{path}/app/views"] if File.directory?("#{path}/app/views") + end - def init_path - File.join(directory, 'rails', 'init.rb') + initializer :add_routing_file, :after => :initialize_routing do |app| + routing_file = "#{path}/config/routes.rb" + if File.exist?(routing_file) + app.routes.add_configuration_file(routing_file) + app.routes.reload! + end + end end end -end +end
\ No newline at end of file diff --git a/railties/lib/rails/plugin/loader.rb b/railties/lib/rails/plugin/loader.rb deleted file mode 100644 index 4808c6ad57..0000000000 --- a/railties/lib/rails/plugin/loader.rb +++ /dev/null @@ -1,200 +0,0 @@ -require "rails/plugin" - -module Rails - class Plugin - class Loader - attr_reader :initializer - - # Creates a new Plugin::Loader instance, associated with the given - # Rails::Initializer. This default implementation automatically locates - # all plugins, and adds all plugin load paths, when it is created. The plugins - # are then fully loaded (init.rb is evaluated) when load_plugins is called. - # - # It is the loader's responsibility to ensure that only the plugins specified - # in the configuration are actually loaded, and that the order defined - # is respected. - def initialize(initializer) - @initializer = initializer - end - - # Returns the plugins to be loaded, in the order they should be loaded. - def plugins - @plugins ||= all_plugins.select { |plugin| should_load?(plugin) }.sort { |p1, p2| order_plugins(p1, p2) } - end - - # Returns the plugins that are in engine-form (have an app/ directory) - def engines - @engines ||= plugins.select {|plugin| plugin.engine? } - end - - # Returns all the plugins that could be found by the current locators. - def all_plugins - @all_plugins ||= locate_plugins - @all_plugins - end - - def load_plugins - plugins.each do |plugin| - plugin.load(initializer) - register_plugin_as_loaded(plugin) - end - - configure_engines - - ensure_all_registered_plugins_are_loaded! - end - - # Adds the load paths for every plugin into the $LOAD_PATH. Plugin load paths are - # added *after* the application's <tt>lib</tt> directory, to ensure that an application - # can always override code within a plugin. - # - # Plugin load paths are also added to Dependencies.load_paths, and Dependencies.load_once_paths. - def add_plugin_load_paths - plugins.each do |plugin| - plugin.load_paths.each do |path| - $LOAD_PATH.insert(application_lib_index + 1, path) - - ActiveSupport::Dependencies.load_paths << path - - unless configuration.reload_plugins? - ActiveSupport::Dependencies.load_once_paths << path - end - end - end - - $LOAD_PATH.uniq! - end - - def engine_metal_paths - engines.collect {|engine| engine.metal_path } - end - - protected - def configure_engines - if engines.any? - add_engine_routing_configurations - add_engine_locales - add_engine_controller_paths - add_engine_view_paths - end - end - - def add_engine_routing_configurations - engines.select {|engine| engine.routed? }.map {|engine| engine.routing_file }.each do |routing_file| - ActionController::Routing::Routes.add_configuration_file(routing_file) - end - end - - def add_engine_locales - localized_engines = engines.select { |engine| engine.localized? } - - # reverse it such that the last engine can overwrite translations from the first, like with routes - locale_files = localized_engines.collect { |engine| engine.locale_files }.reverse.flatten - I18n.load_path += locale_files - I18n.load_path - end - - def add_engine_controller_paths - ActionController::Routing.controller_paths += engines.collect {|engine| engine.controller_path } - end - - def add_engine_view_paths - # reverse it such that the last engine can overwrite view paths from the first, like with routes - paths = ActionView::PathSet.new(engines.collect {|engine| engine.view_path }.reverse) - ActionController::Base.view_paths.concat(paths) - ActionMailer::Base.view_paths.concat(paths) if configuration.frameworks.include?(:action_mailer) - end - - # The locate_plugins method uses each class in config.plugin_locators to - # find the set of all plugins available to this Rails application. - def locate_plugins - configuration.plugin_locators.map do |locator| - locator.new(initializer).plugins - end.flatten - # TODO: sorting based on config.plugins - end - - def register_plugin_as_loaded(plugin) - initializer.config.loaded_plugins << plugin - end - - def configuration - initializer.configuration - end - - def should_load?(plugin) - # uses Plugin#name and Plugin#valid? - enabled?(plugin) && plugin.valid? - end - - def order_plugins(plugin_a, plugin_b) - if !explicit_plugin_loading_order? - plugin_a <=> plugin_b - else - if !explicitly_enabled?(plugin_a) && !explicitly_enabled?(plugin_b) - plugin_a <=> plugin_b - else - effective_order_of(plugin_a) <=> effective_order_of(plugin_b) - end - end - end - - def effective_order_of(plugin) - if explicitly_enabled?(plugin) - registered_plugin_names.index(plugin.name) - else - registered_plugin_names.index('all') - end - end - - def application_lib_index - $LOAD_PATH.index(File.join(Rails.root, 'lib')) || 0 - end - - def enabled?(plugin) - !explicit_plugin_loading_order? || registered?(plugin) - end - - def explicit_plugin_loading_order? - !registered_plugin_names.nil? - end - - def registered?(plugin) - explicit_plugin_loading_order? && registered_plugins_names_plugin?(plugin) - end - - def explicitly_enabled?(plugin) - !explicit_plugin_loading_order? || explicitly_registered?(plugin) - end - - def explicitly_registered?(plugin) - explicit_plugin_loading_order? && registered_plugin_names.include?(plugin.name) - end - - def registered_plugins_names_plugin?(plugin) - registered_plugin_names.include?(plugin.name) || registered_plugin_names.include?('all') - end - - # The plugins that have been explicitly listed with config.plugins. If this list is nil - # then it means the client does not care which plugins or in what order they are loaded, - # so we load all in alphabetical order. If it is an empty array, we load no plugins, if it is - # non empty, we load the named plugins in the order specified. - def registered_plugin_names - configuration.plugins ? configuration.plugins.map {|plugin| plugin.to_s } : nil - end - - def loaded?(plugin_name) - initializer.config.loaded_plugins.detect { |plugin| plugin.name == plugin_name.to_s } - end - - def ensure_all_registered_plugins_are_loaded! - if explicit_plugin_loading_order? - if configuration.plugins.detect {|plugin| plugin != :all && !loaded?(plugin) } - missing_plugins = configuration.plugins - (plugins.map{|p| p.name.to_sym} + [:all]) - raise LoadError, "Could not locate the following plugins: #{missing_plugins.to_sentence(:locale => :en)}" - end - end - end - - end - end -end diff --git a/railties/lib/rails/plugin/locator.rb b/railties/lib/rails/plugin/locator.rb deleted file mode 100644 index 56cbaf37c6..0000000000 --- a/railties/lib/rails/plugin/locator.rb +++ /dev/null @@ -1,100 +0,0 @@ -module Rails - class Plugin - - # The Plugin::Locator class should be subclasses to provide custom plugin-finding - # abilities to Rails (i.e. loading plugins from Gems, etc). Each subclass should implement - # the <tt>located_plugins</tt> method, which return an array of Plugin objects that have been found. - class Locator - include Enumerable - - attr_reader :initializer - - def initialize(initializer) - @initializer = initializer - end - - # This method should return all the plugins which this Plugin::Locator can find - # These will then be used by the current Plugin::Loader, which is responsible for actually - # loading the plugins themselves - def plugins - raise "The `plugins' method must be defined by concrete subclasses of #{self.class}" - end - - def each(&block) - plugins.each(&block) - end - - def plugin_names - plugins.map {|plugin| plugin.name } - end - end - - # The Rails::Plugin::FileSystemLocator will try to locate plugins by examining the directories - # in the paths given in configuration.plugin_paths. Any plugins that can be found are returned - # in a list. - # - # The criteria for a valid plugin in this case is found in Rails::Plugin#valid?, although - # other subclasses of Rails::Plugin::Locator can of course use different conditions. - class FileSystemLocator < Locator - - # Returns all the plugins which can be loaded in the filesystem, under the paths given - # by configuration.plugin_paths. - def plugins - initializer.configuration.plugin_paths.flatten.inject([]) do |plugins, path| - plugins.concat locate_plugins_under(path) - plugins - end.flatten - end - - private - - # Attempts to create a plugin from the given path. If the created plugin is valid? - # (see Rails::Plugin#valid?) then the plugin instance is returned; otherwise nil. - def create_plugin(path) - plugin = Rails::Plugin.new(path) - plugin.valid? ? plugin : nil - end - - # This starts at the base path looking for valid plugins (see Rails::Plugin#valid?). - # Since plugins can be nested arbitrarily deep within an unspecified number of intermediary - # directories, this method runs recursively until it finds a plugin directory, e.g. - # - # locate_plugins_under('vendor/plugins/acts/acts_as_chunky_bacon') - # => <Rails::Plugin name: 'acts_as_chunky_bacon' ... > - # - def locate_plugins_under(base_path) - Dir.glob(File.join(base_path, '*')).sort.inject([]) do |plugins, path| - if plugin = create_plugin(path) - plugins << plugin - elsif File.directory?(path) - plugins.concat locate_plugins_under(path) - end - plugins - end - end - end - - # The GemLocator scans all the loaded RubyGems, looking for gems with - # a <tt>rails/init.rb</tt> file. - class GemLocator < Locator - def plugins - gem_index = {} - specs = gem_index.keys - specs += Gem.loaded_specs.values.select do |spec| - spec.loaded_from && # prune stubs - File.exist?(File.join(spec.full_gem_path, "rails", "init.rb")) - end - specs.compact! - - require "rubygems/dependency_list" - - deps = Gem::DependencyList.new - deps.add(*specs) unless specs.empty? - - deps.dependency_order.collect do |spec| - Rails::GemPlugin.new(spec, gem_index[spec]) - end - end - end - end -end
\ No newline at end of file diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb index aa2711c616..0a7b405553 100644 --- a/railties/lib/rails/rack/debugger.rb +++ b/railties/lib/rails/rack/debugger.rb @@ -4,6 +4,8 @@ module Rails def initialize(app) @app = app + ARGV.clear # clear ARGV so that script/server options aren't passed to IRB + require_library_or_gem 'ruby-debug' ::Debugger.start ::Debugger.settings[:autoeval] = true if ::Debugger.respond_to?(:settings) diff --git a/railties/lib/rails/rack/static.rb b/railties/lib/rails/rack/static.rb index d6b8face27..ebe8b9e103 100644 --- a/railties/lib/rails/rack/static.rb +++ b/railties/lib/rails/rack/static.rb @@ -1,5 +1,5 @@ require 'action_dispatch' module Rails::Rack - Static = Deprecation::DeprecatedConstantProxy.new('Rails::Rack::Static', ActionDispatch::Static) + Static = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Rails::Rack::Static', ActionDispatch::Static) end diff --git a/railties/test/application/plugins_test.rb b/railties/test/application/plugins_test.rb deleted file mode 100644 index 80a19856d7..0000000000 --- a/railties/test/application/plugins_test.rb +++ /dev/null @@ -1,117 +0,0 @@ -require "isolation/abstract_unit" - -module ApplicationTests - class PluginTest < Test::Unit::TestCase - include ActiveSupport::Testing::Isolation - - def assert_plugins(list_of_names, array_of_plugins, message=nil) - assert_equal list_of_names.map { |n| n.to_s }, array_of_plugins.map { |p| p.name }, message - end - - def setup - build_app - boot_rails - require "rails" - @failure_tip = "It's likely someone has added a new plugin fixture without updating this list" - # Tmp hax to get tests working - FileUtils.cp_r "#{File.dirname(__FILE__)}/../fixtures/plugins", "#{app_path}/vendor" - end - - test "all plugins are loaded when registered plugin list is untouched" do - Rails::Initializer.run { |c| c.root = app_path } - Rails.initialize! - assert_plugins [ - :a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby - ], Rails.application.config.loaded_plugins, @failure_tip - end - - test "no plugins are loaded if the configuration has an empty plugin list" do - Rails::Initializer.run { |c| c.root = app_path; c.plugins = [] } - assert_plugins [], Rails.application.config.loaded_plugins - end - - test "only the specified plugins are located in the order listed" do - plugin_names = [:plugin_with_no_lib_dir, :acts_as_chunky_bacon] - Rails::Initializer.run { |c| c.root = app_path; c.plugins = plugin_names } - Rails.initialize! - assert_plugins plugin_names, Rails.application.config.loaded_plugins - end - - test "all plugins loaded after all" do - Rails::Initializer.run do |config| - config.root = app_path - config.plugins = [:stubby, :all, :acts_as_chunky_bacon] - end - Rails.initialize! - assert_plugins [:stubby, :a, :engine, :gemlike, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], Rails.application.config.loaded_plugins, @failure_tip - end - - test "plugin names may be strings" do - plugin_names = ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir] - Rails::Initializer.run do |config| - config.root = app_path - config.plugins = ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir] - end - Rails.initialize! - - assert_plugins plugin_names, Rails.application.config.loaded_plugins, @failure_tip - end - - test "all plugins loaded when all is used" do - Rails::Initializer.run do |config| - config.root = app_path - config.plugins = [:stubby, :acts_as_chunky_bacon, :all] - end - Rails.initialize! - - assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :engine, :gemlike, :plugin_with_no_lib_dir], Rails.application.config.loaded_plugins, @failure_tip - end - - test "all loaded plugins are added to the load paths" do - Rails::Initializer.run do |config| - config.root = app_path - config.plugins = [:stubby, :acts_as_chunky_bacon] - end - Rails.initialize! - - assert $LOAD_PATH.include?("#{app_path}/vendor/plugins/default/stubby/lib") - assert $LOAD_PATH.include?("#{app_path}/vendor/plugins/default/acts/acts_as_chunky_bacon/lib") - end - - test "registering a plugin name that does not exist raises a load error" do - Rails::Initializer.run do |config| - config.root = app_path - config.plugins = [:stubby, :acts_as_a_non_existant_plugin] - end - - assert_raise(LoadError) do - Rails.initialize! - end - end - - test "load error messages mention missing plugins and no others" do - valid_plugins = [:stubby, :acts_as_chunky_bacon] - invalid_plugins = [:non_existant_plugin1, :non_existant_plugin2] - - begin - Rails::Initializer.run do |config| - config.root = app_path - config.plugins = [:stubby, :acts_as_chunky_bacon, :non_existant_plugin1, :non_existant_plugin2] - end - Rails.initialize! - flunk "Expected a LoadError but did not get one" - rescue LoadError => e - assert_plugins valid_plugins, Rails.application.config.loaded_plugins, @failure_tip - - invalid_plugins.each do |plugin| - assert_match(/#{plugin.to_s}/, e.message, "LoadError message should mention plugin '#{plugin}'") - end - - valid_plugins.each do |plugin| - assert_no_match(/#{plugin.to_s}/, e.message, "LoadError message should not mention '#{plugin}'") - end - end - end - - end -end
\ No newline at end of file diff --git a/railties/test/fixtures/vendor/plugins/mspec/lib/generators/mspec_generator.rb b/railties/test/fixtures/vendor/plugins/mspec/lib/rails_generators/mspec_generator.rb index 191bdbf2fc..191bdbf2fc 100644 --- a/railties/test/fixtures/vendor/plugins/mspec/lib/generators/mspec_generator.rb +++ b/railties/test/fixtures/vendor/plugins/mspec/lib/rails_generators/mspec_generator.rb diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index a8716d9992..4b7b80c7f5 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -150,6 +150,13 @@ class GeneratorsTest < GeneratorsTestCase assert_equal "test_unit:generators:plugin", klass.namespace end + def test_fallbacks_for_generators_on_find_by_namespace_with_context + Rails::Generators.fallbacks[:remarkable] = :test_unit + klass = Rails::Generators.find_by_namespace(:remarkable, :rails, :plugin) + assert klass + assert_equal "test_unit:generators:plugin", klass.namespace + end + def test_fallbacks_for_generators_on_invoke Rails::Generators.fallbacks[:shoulda] = :test_unit TestUnit::Generators::ModelGenerator.expects(:start).with(["Account"], {}) diff --git a/railties/test/initializable_test.rb b/railties/test/initializable_test.rb index 2920883132..a9e60680ac 100644 --- a/railties/test/initializable_test.rb +++ b/railties/test/initializable_test.rb @@ -94,6 +94,10 @@ module InitializableTests include Rails::Initializable initializer :startup, :before => :last do + $arr << 3 + end + + initializer :terminate, :after => :first do $arr << two end @@ -109,7 +113,7 @@ module InitializableTests end initializer :last do - $arr << 3 + $arr << 4 end def self.initializers @@ -189,7 +193,7 @@ module InitializableTests test "merges in the initializers from the parent in the right order" do $arr = [] OverriddenInitializer.new.run_initializers - assert_equal [1, 2, 3], $arr + assert_equal [1, 2, 3, 4], $arr end end end
\ No newline at end of file diff --git a/railties/test/initializer/initialize_i18n_test.rb b/railties/test/initializer/initialize_i18n_test.rb index d664f96ad1..076816d73b 100644 --- a/railties/test/initializer/initialize_i18n_test.rb +++ b/railties/test/initializer/initialize_i18n_test.rb @@ -30,27 +30,27 @@ module InitializerTests end test "i18n finds locale files in engines" do - app_file "vendor/plugins/engine/init.rb", "" - app_file "vendor/plugins/engine/app/models/hellos.rb", "class Hello ; end" - app_file "vendor/plugins/engine/lib/omg.rb", "puts 'omg'" - app_file "vendor/plugins/engine/config/locales/en.yml", "hello:" - - Rails::Initializer.run do |c| - c.root = app_path - c.i18n.load_path << "my/other/locale.yml" - end - Rails.initialize! - - #{RAILS_FRAMEWORK_ROOT}/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml - assert_equal %W( - #{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/locale/en.yml - #{RAILS_FRAMEWORK_ROOT}/activemodel/lib/active_model/locale/en.yml - #{RAILS_FRAMEWORK_ROOT}/activerecord/lib/active_record/locale/en.yml - #{RAILS_FRAMEWORK_ROOT}/actionpack/lib/action_view/locale/en.yml - #{app_path}/config/locales/en.yml - my/other/locale.yml - #{app_path}/vendor/plugins/engine/config/locales/en.yml - ).map { |path| File.expand_path(path) }, I18n.load_path.map { |path| File.expand_path(path) } + # app_file "vendor/plugins/engine/init.rb", "" + # app_file "vendor/plugins/engine/app/models/hellos.rb", "class Hello ; end" + # app_file "vendor/plugins/engine/lib/omg.rb", "puts 'omg'" + # app_file "vendor/plugins/engine/config/locales/en.yml", "hello:" + # + # Rails::Initializer.run do |c| + # c.root = app_path + # c.i18n.load_path << "my/other/locale.yml" + # end + # Rails.initialize! + # + # #{RAILS_FRAMEWORK_ROOT}/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml + # assert_equal %W( + # #{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/locale/en.yml + # #{RAILS_FRAMEWORK_ROOT}/activemodel/lib/active_model/locale/en.yml + # #{RAILS_FRAMEWORK_ROOT}/activerecord/lib/active_record/locale/en.yml + # #{RAILS_FRAMEWORK_ROOT}/actionpack/lib/action_view/locale/en.yml + # #{app_path}/config/locales/en.yml + # my/other/locale.yml + # #{app_path}/vendor/plugins/engine/config/locales/en.yml + # ).map { |path| File.expand_path(path) }, I18n.load_path.map { |path| File.expand_path(path) } end end end
\ No newline at end of file diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 0b479e944c..ba8b35d5cc 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -92,6 +92,34 @@ module TestHelpers add_to_config 'config.action_controller.session = { :key => "_myapp_session", :secret => "bac838a849c1d5c4de2e6a50af826079" }' end + class Bukkit + def initialize(path) + @path = path + end + + def write(file, string) + path = "#{@path}/#{file}" + FileUtils.mkdir_p(File.dirname(path)) + File.open(path, "w") {|f| f.puts string } + end + + def delete(file) + File.delete("#{@path}/#{file}") + end + end + + def plugin(name, string = "") + dir = "#{app_path}/vendor/plugins/#{name}" + FileUtils.mkdir_p(dir) + File.open("#{dir}/init.rb", 'w') do |f| + f.puts "::#{name.upcase} = 'loaded'" + f.puts string + end + Bukkit.new(dir).tap do |bukkit| + yield bukkit if block_given? + end + end + def script(script) Dir.chdir(app_path) do `#{Gem.ruby} #{app_path}/script/#{script}` @@ -157,7 +185,7 @@ Module.new do FileUtils.mkdir(tmp_path) environment = File.expand_path('../../../../vendor/gems/environment', __FILE__) - if File.exist?(environment) + if File.exist?("#{environment}.rb") require_environment = "-r #{environment}" end diff --git a/railties/test/plugin_loader_test.rb b/railties/test/plugin_loader_test.rb deleted file mode 100644 index 0b43c49bb2..0000000000 --- a/railties/test/plugin_loader_test.rb +++ /dev/null @@ -1,176 +0,0 @@ -require 'plugin_test_helper' - -$:.unshift File.dirname(__FILE__) + "/../../actionpack/lib" -$:.unshift File.dirname(__FILE__) + "/../../actionmailer/lib" -require 'action_controller' -require 'action_mailer' - -# TODO: Rewrite all these tests -class FakeInitializerSlashApplication - attr_reader :config - alias configuration config - - def initialize - @config = Rails::Configuration.new - end -end - -class TestPluginLoader < Test::Unit::TestCase - ORIGINAL_LOAD_PATH = $LOAD_PATH.dup - - def setup - reset_load_path! - - @initializer = FakeInitializerSlashApplication.new - @configuration = @initializer.config - Rails.application = @initializer - @configuration.plugin_paths << plugin_fixture_root_path - @valid_plugin_path = plugin_fixture_path('default/stubby') - @empty_plugin_path = plugin_fixture_path('default/empty') - - @failure_tip = "It's likely someone has added a new plugin fixture without updating this list" - - @loader = Rails::Plugin::Loader.new(@initializer) - end - - def test_should_locate_plugins_by_asking_each_locator_specifed_in_configuration_for_its_plugins_result - locator_1 = stub(:plugins => [:a, :b, :c]) - locator_2 = stub(:plugins => [:d, :e, :f]) - locator_class_1 = stub(:new => locator_1) - locator_class_2 = stub(:new => locator_2) - @configuration.plugin_locators = [locator_class_1, locator_class_2] - assert_equal [:a, :b, :c, :d, :e, :f], @loader.send(:locate_plugins) - end - - def test_should_memoize_the_result_of_locate_plugins_as_all_plugins - plugin_list = [:a, :b, :c] - @loader.expects(:locate_plugins).once.returns(plugin_list) - assert_equal plugin_list, @loader.all_plugins - assert_equal plugin_list, @loader.all_plugins # ensuring that locate_plugins isn't called again - end - - def test_should_return_empty_array_if_configuration_plugins_is_empty - @configuration.plugins = [] - assert_equal [], @loader.plugins - end - - def test_should_find_all_availble_plugins_and_return_as_all_plugins - assert_plugins [ :engine, :stubby, :plugin_with_no_lib_dir, :gemlike, :acts_as_chunky_bacon, :a], @loader.all_plugins.reverse, @failure_tip - end - - def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_untouched - assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip - end - - def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_nil - @configuration.plugins = nil - assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip - end - - def test_should_return_specific_plugins_named_in_config_plugins_array_if_set - plugin_names = [:acts_as_chunky_bacon, :stubby] - only_load_the_following_plugins! plugin_names - assert_plugins plugin_names, @loader.plugins - end - - def test_should_respect_the_order_of_plugins_given_in_configuration - plugin_names = [:stubby, :acts_as_chunky_bacon] - only_load_the_following_plugins! plugin_names - assert_plugins plugin_names, @loader.plugins - end - - def test_should_load_all_plugins_in_natural_order_when_all_is_used - only_load_the_following_plugins! [:all] - assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip - end - - def test_should_load_specified_plugins_in_order_and_then_all_remaining_plugins_when_all_is_used - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon, :all] - assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :engine, :gemlike, :plugin_with_no_lib_dir], @loader.plugins, @failure_tip - end - - def test_should_be_able_to_specify_loading_of_plugins_loaded_after_all - only_load_the_following_plugins! [:stubby, :all, :acts_as_chunky_bacon] - assert_plugins [:stubby, :a, :engine, :gemlike, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], @loader.plugins, @failure_tip - end - - def test_should_accept_plugin_names_given_as_strings - only_load_the_following_plugins! ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir] - assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :plugin_with_no_lib_dir], @loader.plugins, @failure_tip - end - - def test_should_add_plugin_load_paths_to_global_LOAD_PATH_array - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] - stubbed_application_lib_index_in_LOAD_PATHS = 4 - @loader.stubs(:application_lib_index).returns(stubbed_application_lib_index_in_LOAD_PATHS) - - @loader.add_plugin_load_paths - - assert $LOAD_PATH.index(File.join(plugin_fixture_path('default/stubby'), 'lib')) >= stubbed_application_lib_index_in_LOAD_PATHS - assert $LOAD_PATH.index(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) >= stubbed_application_lib_index_in_LOAD_PATHS - end - - def test_should_add_plugin_load_paths_to_Dependencies_load_paths - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] - - @loader.add_plugin_load_paths - - assert ActiveSupport::Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) - assert ActiveSupport::Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) - end - - def test_should_add_engine_load_paths_to_Dependencies_load_paths - only_load_the_following_plugins! [:engine] - - @loader.add_plugin_load_paths - - %w( models controllers metal helpers ).each do |app_part| - assert ActiveSupport::Dependencies.load_paths.include?( - File.join(plugin_fixture_path('engines/engine'), 'app', app_part) - ), "Couldn't find #{app_part} in load path" - end - end - - def test_engine_controllers_and_action_mailers_should_have_their_view_path_set_when_loaded - only_load_the_following_plugins!([ :engine ]) - - @loader.send :add_engine_view_paths - - assert_equal [ File.join(plugin_fixture_path('engines/engine'), 'app', 'views') ], ActionController::Base.view_paths.map { |p| p.to_s } - assert_equal [ File.join(plugin_fixture_path('engines/engine'), 'app', 'views') ], ActionMailer::Base.view_paths.map { |p| p.to_s } - end - - def test_should_add_plugin_load_paths_to_Dependencies_load_once_paths - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] - - @loader.add_plugin_load_paths - - assert ActiveSupport::Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) - assert ActiveSupport::Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) - end - - def test_should_add_all_load_paths_from_a_plugin_to_LOAD_PATH_array - plugin_load_paths = ["a", "b"] - plugin = stub(:load_paths => plugin_load_paths) - @loader.stubs(:plugins).returns([plugin]) - - @loader.add_plugin_load_paths - - plugin_load_paths.each { |path| assert $LOAD_PATH.include?(path) } - end - - def test_should_add_locale_files_to_I18n_load_path - only_load_the_following_plugins! [:engine] - - @loader.send :add_engine_locales - - assert I18n.load_path.include?(File.join(plugin_fixture_path('engines/engine'), 'config', 'locales', 'en.yml')) - end - - - private - def reset_load_path! - $LOAD_PATH.clear - ORIGINAL_LOAD_PATH.each { |path| $LOAD_PATH << path } - end -end diff --git a/railties/test/plugin_locator_test.rb b/railties/test/plugin_locator_test.rb deleted file mode 100644 index ef57e7ed4c..0000000000 --- a/railties/test/plugin_locator_test.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'plugin_test_helper' - -# TODO: Rewrite all these tests -class FakeInitializerSlashApplication - attr_reader :config - alias configuration config - - def initialize - @config = Rails::Configuration.new - end -end - -class PluginLocatorTest < Test::Unit::TestCase - def test_should_require_subclasses_to_implement_the_plugins_method - assert_raise(RuntimeError) do - Rails::Plugin::Locator.new(nil).plugins - end - end - - def test_should_iterator_over_plugins_returned_by_plugins_when_calling_each - locator = Rails::Plugin::Locator.new(nil) - locator.stubs(:plugins).returns([:a, :b, :c]) - plugin_consumer = mock - plugin_consumer.expects(:consume).with(:a) - plugin_consumer.expects(:consume).with(:b) - plugin_consumer.expects(:consume).with(:c) - - locator.each do |plugin| - plugin_consumer.consume(plugin) - end - end -end - -class PluginFileSystemLocatorTest < Test::Unit::TestCase - def setup - @initializer = FakeInitializerSlashApplication.new - @configuration = @initializer.config - Rails.application = @initializer - # We need to add our testing plugin directory to the plugin paths so - # the locator knows where to look for our plugins - @configuration.plugin_paths << plugin_fixture_root_path - @locator = Rails::Plugin::FileSystemLocator.new(@initializer) - @valid_plugin_path = plugin_fixture_path('default/stubby') - @empty_plugin_path = plugin_fixture_path('default/empty') - end - - def test_should_return_rails_plugin_instances_when_calling_create_plugin_with_a_valid_plugin_directory - assert_kind_of Rails::Plugin, @locator.send(:create_plugin, @valid_plugin_path) - end - - def test_should_return_nil_when_calling_create_plugin_with_an_invalid_plugin_directory - assert_nil @locator.send(:create_plugin, @empty_plugin_path) - end - - def test_should_return_all_plugins_found_under_the_set_plugin_paths - assert_equal ["a", "acts_as_chunky_bacon", "engine", "gemlike", "plugin_with_no_lib_dir", "stubby"].sort, @locator.plugins.map { |p| p.name }.sort - end - - def test_should_find_plugins_only_under_the_plugin_paths_set_in_configuration - @configuration.plugin_paths = [File.join(plugin_fixture_root_path, "default")] - assert_equal ["acts_as_chunky_bacon", "gemlike", "plugin_with_no_lib_dir", "stubby"].sort, @locator.plugins.map { |p| p.name }.sort - - @configuration.plugin_paths = [File.join(plugin_fixture_root_path, "alternate")] - assert_equal ["a"], @locator.plugins.map { |p| p.name } - end - - def test_should_not_raise_any_error_and_return_no_plugins_if_the_plugin_path_value_does_not_exist - @configuration.plugin_paths = ["some_missing_directory"] - assert_nothing_raised do - assert @locator.plugins.empty? - end - end -end diff --git a/railties/test/plugin_test.rb b/railties/test/plugin_test.rb deleted file mode 100644 index 199adcfe39..0000000000 --- a/railties/test/plugin_test.rb +++ /dev/null @@ -1,174 +0,0 @@ -require 'plugin_test_helper' - -# TODO: Rewrite all these tests -class FakeInitializerSlashApplication - attr_reader :config - alias configuration config - - def initialize - @config = Rails::Configuration.new - end -end - -class PluginTest < Test::Unit::TestCase - def setup - @initializer = FakeInitializerSlashApplication.new - @configuration = @initializer.config - Rails.application = @initializer - @valid_plugin_path = plugin_fixture_path('default/stubby') - @empty_plugin_path = plugin_fixture_path('default/empty') - @gemlike_plugin_path = plugin_fixture_path('default/gemlike') - end - - def test_should_determine_plugin_name_from_the_directory_of_the_plugin - assert_equal 'stubby', plugin_for(@valid_plugin_path).name - assert_equal 'empty', plugin_for(@empty_plugin_path).name - end - - def test_should_not_be_loaded_when_created - assert !plugin_for(@valid_plugin_path).loaded? - end - - def test_should_be_marked_as_loaded_when_load_is_called - plugin = plugin_for(@valid_plugin_path) - assert !plugin.loaded? - plugin.stubs(:evaluate_init_rb) - assert_nothing_raised do - plugin.send(:load, anything) - end - assert plugin.loaded? - end - - def test_should_determine_validity_of_given_path - # This is a plugin path, with a lib dir - assert plugin_for(@valid_plugin_path).valid? - # This just has an init.rb and no lib dir - assert plugin_for(plugin_fixture_path('default/plugin_with_no_lib_dir')).valid? - # This would be a plugin path, but the directory is empty - assert !plugin_for(plugin_fixture_path('default/empty')).valid? - # This is a non sense path - assert !plugin_for(plugin_fixture_path('default/this_directory_does_not_exist')).valid? - end - - def test_should_return_empty_array_for_load_paths_when_plugin_has_no_lib_directory - assert_equal [], plugin_for(plugin_fixture_path('default/plugin_with_no_lib_dir')).load_paths - end - - def test_should_return_array_with_lib_path_for_load_paths_when_plugin_has_a_lib_directory - expected_lib_dir = File.join(plugin_fixture_path('default/stubby'), 'lib') - assert_equal [expected_lib_dir], plugin_for(plugin_fixture_path('default/stubby')).load_paths - end - - def test_should_raise_a_load_error_when_trying_to_determine_the_load_paths_from_an_invalid_plugin - assert_nothing_raised do - plugin_for(@valid_plugin_path).load_paths - end - - assert_raise(LoadError) do - plugin_for(@empty_plugin_path).load_paths - end - - assert_raise(LoadError) do - plugin_for('this_is_not_a_plugin_directory').load_paths - end - end - - def test_should_raise_a_load_error_when_trying_to_load_an_invalid_plugin - # This path is fine so nothing is raised - assert_nothing_raised do - plugin = plugin_for(@valid_plugin_path) - plugin.stubs(:evaluate_init_rb) - plugin.send(:load, @initializer) - end - - # This path is fine so nothing is raised - assert_nothing_raised do - plugin = plugin_for(@gemlike_plugin_path) - plugin.stubs(:evaluate_init_rb) - plugin.send(:load, @initializer) - end - - # This is an empty path so it raises - assert_raise(LoadError) do - plugin = plugin_for(@empty_plugin_path) - plugin.stubs(:evaluate_init_rb) - plugin.send(:load, @initializer) - end - - assert_raise(LoadError) do - plugin = plugin_for('this_is_not_a_plugin_directory') - plugin.stubs(:evaluate_init_rb) - plugin.send(:load, @initializer) - end - end - - def test_should_raise_a_load_error_when_trying_to_access_load_paths_of_an_invalid_plugin - # This path is fine so nothing is raised - assert_nothing_raised do - plugin_for(@valid_plugin_path).load_paths - end - - # This is an empty path so it raises - assert_raise(LoadError) do - plugin_for(@empty_plugin_path).load_paths - end - - assert_raise(LoadError) do - plugin_for('this_is_not_a_plugin_directory').load_paths - end - end - - def test_loading_a_plugin_gives_the_init_file_access_to_all_it_needs - failure_tip = "Perhaps someone has written another test that loads this same plugin and therefore makes the StubbyMixin constant defined already." - assert !defined?(StubbyMixin), failure_tip - plugin = plugin_for(@valid_plugin_path) - plugin.load_paths.each { |path| $LOAD_PATH.unshift(path) } - # The init.rb of this plugin raises if it doesn't have access to all the things it needs - assert_nothing_raised do - plugin.load(@initializer) - end - assert defined?(StubbyMixin) - end - - def test_should_sort_naturally_by_name - a = plugin_for("path/a") - b = plugin_for("path/b") - z = plugin_for("path/z") - assert_equal [a, b, z], [b, z, a].sort - end - - def test_should_only_be_loaded_once - plugin = plugin_for(@valid_plugin_path) - assert !plugin.loaded? - plugin.expects(:evaluate_init_rb) - assert_nothing_raised do - plugin.send(:load, @initializer) - plugin.send(:load, @initializer) - end - assert plugin.loaded? - end - - def test_should_make_about_yml_available_as_about_method_on_plugin - plugin = plugin_for(@valid_plugin_path) - assert_equal "Plugin Author", plugin.about['author'] - assert_equal "1.0.0", plugin.about['version'] - end - - def test_should_return_empty_hash_for_about_if_about_yml_is_missing - assert_equal({}, plugin_for(about_yml_plugin_path('plugin_without_about_yaml')).about) - end - - def test_should_return_empty_hash_for_about_if_about_yml_is_malformed - assert_equal({}, plugin_for(about_yml_plugin_path('bad_about_yml')).about) - end - - private - - def about_yml_plugin_path(name) - File.join(File.dirname(__FILE__), 'fixtures', 'about_yml_plugins', name) - end - - def plugin_for(path) - Rails::Plugin.new(path) - end -end diff --git a/railties/test/plugin_test_helper.rb b/railties/test/plugin_test_helper.rb deleted file mode 100644 index 93004e0ddf..0000000000 --- a/railties/test/plugin_test_helper.rb +++ /dev/null @@ -1,29 +0,0 @@ -$:.unshift File.dirname(__FILE__) + "/../lib" -$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib" - -require 'test/unit' -require 'active_support' -require 'rails/initializer' -require 'abstract_unit' - -# We need to set RAILS_ROOT if it isn't already set -RAILS_ROOT = '.' unless defined?(RAILS_ROOT) - -class Test::Unit::TestCase - private - def plugin_fixture_root_path - File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'plugins')) - end - - def only_load_the_following_plugins!(plugins) - @initializer.configuration.plugins = plugins - end - - def plugin_fixture_path(path) - File.join(plugin_fixture_root_path, path) - end - - def assert_plugins(list_of_names, array_of_plugins, message=nil) - assert_equal list_of_names.map { |n| n.to_s }, array_of_plugins.map { |p| p.name }, message - end -end diff --git a/railties/test/plugins/vendored_test.rb b/railties/test/plugins/vendored_test.rb index 71de542ff7..9a2d40cad8 100644 --- a/railties/test/plugins/vendored_test.rb +++ b/railties/test/plugins/vendored_test.rb @@ -1,19 +1,195 @@ require "isolation/abstract_unit" -module ApplicationTests - class PluginTest < Test::Unit::TestCase +module PluginsTest + class VendoredTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation def setup build_app - end - test "generates the plugin" do - script "generate plugin my_plugin" - File.open("#{app_path}/vendor/plugins/my_plugin/init.rb", 'w') do |f| - f.puts "OMG = 'hello'" + @plugin = plugin "bukkits", "::LEVEL = config.log_level" do |plugin| + plugin.write "lib/bukkits.rb", "class Bukkits; end" end + end + + def boot_rails + super + require "#{app_path}/config/environment" + end + + test "it loads the plugin's init.rb file" do + boot_rails + assert_equal "loaded", BUKKITS + end + + test "the init.rb file has access to the config object" do + boot_rails + assert_equal :debug, LEVEL + end + + test "the plugin puts its lib directory on the load path" do + boot_rails + require "bukkits" + assert_equal "Bukkits", Bukkits.name + end + + test "plugin paths get added to the AS::Dependency list" do + boot_rails + assert_equal "Bukkits", Bukkits.name + end + + test "plugin constants do not get reloaded by default" do + boot_rails + assert_equal "Bukkits", Bukkits.name + ActiveSupport::Dependencies.clear + @plugin.delete("lib/bukkits.rb") + assert_nothing_raised { Bukkits } + end + + test "plugin constants get reloaded if config.reload_plugins is set" do + add_to_config <<-RUBY + config.reload_plugins = true + RUBY + + boot_rails + + assert_equal "Bukkits", Bukkits.name + ActiveSupport::Dependencies.clear + @plugin.delete("lib/bukkits.rb") + assert_raises(NameError) { Bukkits } + end + + test "plugin should work without init.rb" do + @plugin.delete("init.rb") + + boot_rails + + require "bukkits" + assert_nothing_raised { Bukkits } + end + + test "the plugin puts its models directory on the load path" do + @plugin.write "app/models/my_bukkit.rb", "class MyBukkit ; end" + + boot_rails + + assert_nothing_raised { MyBukkit } + end + + test "the plugin puts is controllers directory on the load path" do + @plugin.write "app/controllers/bukkit_controller.rb", "class BukkitController ; end" + + boot_rails + + assert_nothing_raised { BukkitController } + end + + test "the plugin adds its view to the load path" do + @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY + class BukkitController < ActionController::Base + def index + end + end + RUBY + + @plugin.write "app/views/bukkit/index.html.erb", "Hello bukkits" + + boot_rails + + require "action_controller" + require "rack/mock" + response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/")) + assert_equal "Hello bukkits\n", response[2].body + end + + test "the plugin adds helpers to the controller's views" do + @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY + class BukkitController < ActionController::Base + def index + end + end + RUBY + + @plugin.write "app/helpers/bukkit_helper.rb", <<-RUBY + module BukkitHelper + def bukkits + "bukkits" + end + end + RUBY + + @plugin.write "app/views/bukkit/index.html.erb", "Hello <%= bukkits %>" + + boot_rails + + require "rack/mock" + response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/")) + assert_equal "Hello bukkits\n", response[2].body + end + + test "routes.rb are added to the router" do + @plugin.write "config/routes.rb", <<-RUBY + class Sprokkit + def self.call(env) + [200, {'Content-Type' => 'text/html'}, ["I am a Sprokkit"]] + end + end + + ActionController::Routing::Routes.draw do + match "/sprokkit", :to => Sprokkit + end + RUBY + + boot_rails + require "rack/mock" + response = Rails.application.call(Rack::MockRequest.env_for("/sprokkit")) + assert_equal "I am a Sprokkit", response[2].join + end + end + + class VendoredOrderingTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + $arr = [] + plugin "a_plugin", "$arr << :a" + plugin "b_plugin", "$arr << :b" + plugin "c_plugin", "$arr << :c" + end + + def boot_rails + super require "#{app_path}/config/environment" end + + test "plugins are loaded alphabetically by default" do + boot_rails + assert_equal [:a, :b, :c], $arr + end + + test "if specified, only those plugins are loaded" do + add_to_config "config.plugins = [:b_plugin]" + boot_rails + assert_equal [:b], $arr + end + + test "the plugins are initialized in the order they are specified" do + add_to_config "config.plugins = [:b_plugin, :a_plugin]" + boot_rails + assert_equal [:b, :a], $arr + end + + test "if :all is specified, the remaining plugins are loaded in alphabetical order" do + add_to_config "config.plugins = [:c_plugin, :all]" + boot_rails + assert_equal [:c, :a, :b], $arr + end + + test "if :all is at the beginning, it represents the plugins not otherwise specified" do + add_to_config "config.plugins = [:all, :b_plugin]" + boot_rails + assert_equal [:a, :c, :b], $arr + end end end
\ No newline at end of file diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index dcf9966c0d..fc28d7e912 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -1,15 +1,4 @@ -$:.unshift File.dirname(__FILE__) + "/../lib" -$:.unshift File.dirname(__FILE__) + "/../builtin/rails_info" -$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib" -$:.unshift File.dirname(__FILE__) + "/../../actionpack/lib" - -require 'rubygems' -gem 'rack', '~> 1.0.0' - -require 'test/unit' -require 'active_support' -require 'active_support/test_case' -require 'action_controller' +require 'abstract_unit' unless defined?(Rails) && defined?(Rails::Info) module Rails |