diff options
42 files changed, 316 insertions, 155 deletions
@@ -24,6 +24,10 @@ platforms :mri_18 do gem "ruby-debug", ">= 0.10.3" end +platforms :mri_19 do + gem "ruby-debug19" +end + platforms :ruby do gem 'json' gem 'yajl-ruby' diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 8fd77aeb17..5b219540f4 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |s| s.add_dependency('i18n', '~> 0.4.1') s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.4') - s.add_dependency('rack-mount', '~> 0.6.12') + s.add_dependency('rack-mount', '~> 0.6.13') s.add_dependency('tzinfo', '~> 0.3.23') s.add_dependency('erubis', '~> 2.6.6') end diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb index d581399514..0eaad2b911 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb @@ -77,9 +77,7 @@ module HTML #:nodoc: # Return a textual representation of the node. def to_s - s = "" - @children.each { |child| s << child.to_s } - s + @children.join() end # Return false (subclasses must override this to provide specific matching diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index a3af37947a..ed066ad75e 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -8,10 +8,5 @@ module ActionDispatch config.action_dispatch.ip_spoofing_check = true config.action_dispatch.show_exceptions = true config.action_dispatch.best_standards_support = true - - # Prepare dispatcher callbacks and run 'prepare' callbacks - initializer "action_dispatch.prepare_dispatcher" do |app| - ActionDispatch::Callbacks.to_prepare { app.routes_reloader.execute_if_updated } - end end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index a3bd4771c2..a2570cb877 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -909,6 +909,11 @@ module ActionDispatch return true end + if resource_scope? + nested { send(method, resources.pop, options, &block) } + return true + end + options.keys.each do |k| (options[:constraints] ||= {})[k] = options.delete(k) if options[k].is_a?(Regexp) end @@ -925,13 +930,6 @@ module ActionDispatch options.merge!(scope_action_options) if scope_action_options? end - if resource_scope? - nested do - send(method, resources.pop, options, &block) - end - return true - end - false end @@ -1017,11 +1015,11 @@ module ActionDispatch end def id_constraint? - @scope[:id].is_a?(Regexp) || (@scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp)) + @scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp) end def id_constraint - @scope[:id] || @scope[:constraints][:id] + @scope[:constraints][:id] end def canonical_action?(action, flag) diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index c0d7423682..5f9dc70766 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -36,6 +36,10 @@ module ActionView autoload :Context autoload :Template autoload :Helpers + autoload :Base + autoload :LookupContext + autoload :PathSet, "action_view/paths" + autoload :TestCase, "action_view/test_case" autoload_under "render" do autoload :Layouts @@ -43,25 +47,26 @@ module ActionView autoload :Rendering end - autoload :Base - autoload :LookupContext - autoload :Resolver, 'action_view/template/resolver' - autoload :PathResolver, 'action_view/template/resolver' - autoload :FileSystemResolver, 'action_view/template/resolver' - autoload :PathSet, 'action_view/paths' + autoload_at "action_view/template/resolver" do + autoload :Resolver + autoload :PathResolver + autoload :FileSystemResolver + end - autoload :MissingTemplate, 'action_view/template/error' - autoload :ActionViewError, 'action_view/template/error' - autoload :EncodingError, 'action_view/template/error' - autoload :TemplateError, 'action_view/template/error' - autoload :WrongEncodingError, 'action_view/template/error' + autoload_at "action_view/template/error" do + autoload :MissingTemplate + autoload :ActionViewError + autoload :EncodingError + autoload :TemplateError + autoload :WrongEncodingError + end - autoload :TemplateHandler, 'action_view/template' - autoload :TemplateHandlers, 'action_view/template' + autoload_at "action_view/template" do + autoload :TemplateHandler + autoload :TemplateHandlers + end end - autoload :TestCase, 'action_view/test_case' - ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index ff25c36fcd..b0a57f47d3 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -188,11 +188,6 @@ module ActionView #:nodoc: delegate :logger, :to => :controller, :allow_nil => true - # TODO: HACK FOR RJS - def view_context - self - end - def self.xss_safe? #:nodoc: true end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 6c3d2cf1b8..94dc25eb85 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -202,6 +202,12 @@ module ActionView # ... # <% end %> # + # You can also set the answer format, like this: + # + # <%= form_for(@post, :format => :json) do |f| %> + # ... + # <% end %> + # # If you have an object that needs to be represented as a different # parameter, like a Client that acts as a Person: # @@ -332,7 +338,9 @@ module ActionView options[:html] ||= {} options[:html].reverse_merge!(html_options) - options[:url] ||= polymorphic_path(object_or_array) + options[:url] ||= options[:format] ? \ + polymorphic_path(object_or_array, :format => options.delete(:format)) : \ + polymorphic_path(object_or_array) end # Creates a scope around a specific model object like form_for, but diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 99f9363a9a..b600666536 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -579,7 +579,7 @@ module ActionView # page.hide 'spinner' # end def update_page(&block) - JavaScriptGenerator.new(view_context, &block).to_s.html_safe + JavaScriptGenerator.new(self, &block).to_s.html_safe end # Works like update_page but wraps the generated JavaScript in a <script> diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 4f14bfe221..b8df2d9a69 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -235,13 +235,8 @@ module ActionView html_options = convert_options_to_data_attributes(options, html_options) url = url_for(options) - if html_options - html_options = html_options.stringify_keys - href = html_options['href'] - tag_options = tag_options(html_options) - else - tag_options = nil - end + href = html_options['href'] + tag_options = tag_options(html_options) href_attr = "href=\"#{html_escape(url)}\"" unless href "<a #{href_attr}#{tag_options}>#{html_escape(name || url)}</a>".html_safe diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index a4b8fafa78..c90c1041ed 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -442,6 +442,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get :preview, :on => :member end + match '/purchases/:token/:filename', + :to => 'purchases#fetch', + :token => /[[:alnum:]]{10}/, + :filename => /(.+)/, + :as => :purchase + + resources :lists, :id => /([A-Za-z0-9]{25})|default/ do + resources :todos, :id => /\d+/ + end + scope '/countries/:country', :constraints => lambda { |params, req| %[all France].include?(params[:country]) } do match '/', :to => 'countries#index' match '/cities', :to => 'countries#cities' @@ -2098,6 +2108,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/customers/1/export', customer_export_path(:customer_id => '1') end + def test_named_character_classes_in_regexp_constraints + get '/purchases/315004be7e/Ruby_on_Rails_3.pdf' + assert_equal 'purchases#fetch', @response.body + assert_equal '/purchases/315004be7e/Ruby_on_Rails_3.pdf', purchase_path(:token => '315004be7e', :filename => 'Ruby_on_Rails_3.pdf') + end + + def test_nested_resource_constraints + get '/lists/01234012340123401234fffff' + assert_equal 'lists#show', @response.body + assert_equal '/lists/01234012340123401234fffff', list_path(:id => '01234012340123401234fffff') + + get '/lists/01234012340123401234fffff/todos/1' + assert_equal 'todos#show', @response.body + assert_equal '/lists/01234012340123401234fffff/todos/1', list_todo_path(:list_id => '01234012340123401234fffff', :id => '1') + + get '/lists/2/todos/1' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ list_todo_path(:list_id => '2', :id => '1') } + end + private def with_test_routes yield diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index fd801e2a9e..9a1fe01872 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -641,6 +641,18 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_with_format + form_for(@post, :format => :json, :html => { :id => "edit_post_123", :class => "edit_post" }) do |f| + concat f.label(:title) + end + + expected = whole_form("/posts/123.json", "edit_post_123" , "edit_post", :method => "put") do + "<label for='post_title'>Title</label>" + end + + assert_dom_equal expected, output_buffer + end + def test_form_for_with_symbol_object_name form_for(@post, :as => "other_name", :html => { :id => 'create-post' }) do |f| concat f.label(:title, :class => 'post_title') @@ -1761,8 +1773,12 @@ class FormHelperTest < ActionView::TestCase "/posts" end - def post_path(post) - "/posts/#{post.id}" + def post_path(post, options = {}) + if options[:format] + "/posts/#{post.id}.#{options[:format]}" + else + "/posts/#{post.id}" + end end def protect_against_forgery? diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 229766612f..c17bec891b 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -69,8 +69,6 @@ module RenderTestCases end def test_render_update - # TODO: You should not have to stub out template because template is self! - @view.instance_variable_set(:@template, @view) assert_equal 'alert("Hello, World!");', @view.render(:update) { |page| page.alert('Hello, World!') } end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 19effbc82f..b76813c554 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -24,13 +24,6 @@ class UrlHelperTest < ActiveSupport::TestCase include ActionView::Context include RenderERBUtils - # self.default_url_options = {:host => "www.example.com"} - - # TODO: This shouldn't be needed (see template.rb:53) - def assigns - {} - end - def hash_for(opts = []) ActiveSupport::OrderedHash[*([:controller, "foo", :action, "bar"].concat(opts))] end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index 2c2ff8f5d5..0d2dd36e59 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -34,6 +34,11 @@ module ActiveModel @observers ||= [] end + # Gets the current observer instances. + def observer_instances + @observer_instances ||= [] + end + # Instantiate the global Active Record observers. def instantiate_observers observers.each { |o| instantiate_observer(o) } @@ -43,20 +48,17 @@ module ActiveModel unless observer.respond_to? :update raise ArgumentError, "observer needs to respond to `update'" end - @observer_instances ||= [] - @observer_instances << observer + observer_instances << observer end def notify_observers(*arg) - if defined? @observer_instances - for observer in @observer_instances - observer.update(*arg) - end + for observer in observer_instances + observer.update(*arg) end end def count_observers - @observer_instances.size + observer_instances.size end protected diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 16ab8e7928..e89385e7e5 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -17,7 +17,7 @@ module ActiveModel def initialize(name, serializable, raw_value=nil) @name, @serializable = name, serializable - @value = value || @serializable.send(name) + @value = raw_value || @serializable.send(name) @type = compute_type end diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index a31966d0c2..2c8a840124 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -4,9 +4,9 @@ module ActiveModel module Validations class ConfirmationValidator < EachValidator def validate_each(record, attribute, value) - confirmed = record.send(:"#{attribute}_confirmation") - return if confirmed.nil? || value == confirmed - record.errors.add(attribute, :confirmation, options) + if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed) + record.errors.add(attribute, :confirmation, options) + end end def setup(klass) diff --git a/activerecord/Rakefile b/activerecord/Rakefile index c2d63cda23..395c72dfbc 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -75,8 +75,8 @@ end namespace :mysql do desc 'Build the MySQL test databases' task :build_databases do - %x( echo "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER}) - %x( echo "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER}) + %x( mysql --user=#{MYSQL_DB_USER} -e "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") + %x( mysql --user=#{MYSQL_DB_USER} -e "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") end desc 'Drop the MySQL test databases' diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index f465a38dbe..c3a34ae104 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -890,6 +890,10 @@ module ActiveRecord #:nodoc: Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup end + def before_remove_const #:nodoc: + reset_scoped_methods + end + private def relation #:nodoc: @@ -1174,6 +1178,10 @@ MSG scoped_methods.last end + def reset_scoped_methods #:nodoc: + Thread.current[:"#{self}_scoped_methods"] = nil + end + # Returns the class type of the record using the current module as a prefix. So descendants of # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass. def compute_type(type_name) diff --git a/activerecord/lib/rails/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb index 5d8a8e81bc..26bc977e19 100644 --- a/activerecord/lib/rails/generators/active_record.rb +++ b/activerecord/lib/rails/generators/active_record.rb @@ -1,27 +1,19 @@ require 'rails/generators/named_base' require 'rails/generators/migration' require 'rails/generators/active_model' +require 'rails/generators/active_record/migration' require 'active_record' module ActiveRecord module Generators class Base < Rails::Generators::NamedBase #:nodoc: include Rails::Generators::Migration + extend ActiveRecord::Generators::Migration # Set the current directory as base for the inherited generators. def self.base_root File.dirname(__FILE__) end - - # Implement the required interface for Rails::Generators::Migration. - def self.next_migration_number(dirname) #:nodoc: - next_migration_number = current_migration_number(dirname) + 1 - if ActiveRecord::Base.timestamped_migrations - [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max - else - "%.3d" % next_migration_number - end - end end end end diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb new file mode 100644 index 0000000000..7f2f2e06a5 --- /dev/null +++ b/activerecord/lib/rails/generators/active_record/migration.rb @@ -0,0 +1,15 @@ +module ActiveRecord + module Generators + module Migration + # Implement the required interface for Rails::Generators::Migration. + def next_migration_number(dirname) #:nodoc: + next_migration_number = current_migration_number(dirname) + 1 + if ActiveRecord::Base.timestamped_migrations + [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max + else + "%.3d" % next_migration_number + end + end + end + end +end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 55f0b1ce21..d58e302cb2 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1415,6 +1415,21 @@ class BasicsTest < ActiveRecord::TestCase end end + def test_default_scope_is_reset + Object.const_set :UnloadablePost, Class.new(ActiveRecord::Base) + UnloadablePost.table_name = 'posts' + UnloadablePost.class_eval do + default_scope order('posts.comments_count ASC') + end + + UnloadablePost.unloadable + assert_not_nil Thread.current[:UnloadablePost_scoped_methods] + ActiveSupport::Dependencies.remove_unloadable_constants! + assert_nil Thread.current[:UnloadablePost_scoped_methods] + ensure + Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost) + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 01152b074f..9823d7aa0e 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -74,9 +74,9 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") ship = pirate.create_ship(:name => 'Nights Dirty Lightning') - assert_no_difference('Ship.count') do - pirate.update_attributes(:ship_attributes => { '_destroy' => true, :id => ship.id }) - end + pirate.update_attributes(:ship_attributes => { '_destroy' => true, :id => ship.id }) + + assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.ship.reload } end def test_a_model_should_respond_to_underscore_destroy_and_return_if_it_is_marked_for_destruction @@ -194,28 +194,30 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy @pirate.ship.destroy + [1, '1', true, 'true'].each do |truth| - @pirate.reload.create_ship(:name => 'Mister Pablo') - assert_difference('Ship.count', -1) do - @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => truth }) - end + ship = @pirate.reload.create_ship(:name => 'Mister Pablo') + @pirate.update_attributes(:ship_attributes => { :id => ship.id, :_destroy => truth }) + + assert_nil @pirate.reload.ship + assert_raise(ActiveRecord::RecordNotFound) { Ship.find(ship.id) } end end def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy [nil, '0', 0, 'false', false].each do |not_truth| - assert_no_difference('Ship.count') do - @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => not_truth }) - end + @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => not_truth }) + + assert_equal @ship, @pirate.reload.ship end end def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? } - assert_no_difference('Ship.count') do - @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => '1' }) - end + @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => '1' }) + + assert_equal @ship, @pirate.reload.ship Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } end @@ -236,12 +238,15 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_not_destroy_the_associated_model_until_the_parent_is_saved - assert_no_difference('Ship.count') do - @pirate.attributes = { :ship_attributes => { :id => @ship.id, :_destroy => '1' } } - end - assert_difference('Ship.count', -1) do - @pirate.save - end + @pirate.attributes = { :ship_attributes => { :id => @ship.id, :_destroy => '1' } } + + assert !@pirate.ship.destroyed? + assert @pirate.ship.marked_for_destruction? + + @pirate.save + + assert @pirate.ship.destroyed? + assert_nil @pirate.reload.ship end def test_should_automatically_enable_autosave_on_the_association @@ -254,29 +259,30 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true @ship.delete - assert_difference('Ship.count', 1) do - @pirate.reload.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower' }) - end + + @pirate.reload.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower' }) + + assert_not_nil @pirate.ship end def test_should_update_existing_when_update_only_is_true_and_no_id_is_given @ship.delete @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning') - assert_no_difference('Ship.count') do - @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower' }) - end + @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower' }) + assert_equal 'Mayflower', @ship.reload.name + assert_equal @ship, @pirate.reload.ship end def test_should_update_existing_when_update_only_is_true_and_id_is_given @ship.delete @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning') - assert_no_difference('Ship.count') do - @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id }) - end + @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id }) + assert_equal 'Mayflower', @ship.reload.name + assert_equal @ship, @pirate.reload.ship end def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction @@ -284,9 +290,11 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase @ship.delete @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning') - assert_difference('Ship.count', -1) do - @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id, :_destroy => true }) - end + @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id, :_destroy => true }) + + assert_nil @pirate.reload.ship + assert_raise(ActiveRecord::RecordNotFound) { Ship.find(@ship.id) } + Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => false end @@ -375,27 +383,24 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy @ship.pirate.destroy [1, '1', true, 'true'].each do |truth| - @ship.reload.create_pirate(:catchphrase => 'Arr') - assert_difference('Pirate.count', -1) do - @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => truth }) - end + pirate = @ship.reload.create_pirate(:catchphrase => 'Arr') + @ship.update_attributes(:pirate_attributes => { :id => pirate.id, :_destroy => truth }) + assert_raise(ActiveRecord::RecordNotFound) { pirate.reload } end end def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy [nil, '0', 0, 'false', false].each do |not_truth| - assert_no_difference('Pirate.count') do - @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => not_truth }) - end + @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => not_truth }) + assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload } end end def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? } - assert_no_difference('Pirate.count') do - @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => '1' }) - end + @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => '1' }) + assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload } Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } end @@ -409,10 +414,12 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase end def test_should_not_destroy_the_associated_model_until_the_parent_is_saved - assert_no_difference('Pirate.count') do - @ship.attributes = { :pirate_attributes => { :id => @ship.pirate.id, '_destroy' => true } } - end - assert_difference('Pirate.count', -1) { @ship.save } + pirate = @ship.pirate + + @ship.attributes = { :pirate_attributes => { :id => pirate.id, '_destroy' => true } } + assert_nothing_raised(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) } + @ship.save + assert_raise(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) } end def test_should_automatically_enable_autosave_on_the_association @@ -421,29 +428,28 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true @pirate.delete - assert_difference('Pirate.count', 1) do - @ship.reload.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr' }) - end + @ship.reload.attributes = { :update_only_pirate_attributes => { :catchphrase => 'Arr' } } + + assert @ship.update_only_pirate.new_record? end def test_should_update_existing_when_update_only_is_true_and_no_id_is_given @pirate.delete @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye') - assert_no_difference('Pirate.count') do - @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr' }) - end + @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr' }) assert_equal 'Arr', @pirate.reload.catchphrase + assert_equal @pirate, @ship.reload.update_only_pirate end def test_should_update_existing_when_update_only_is_true_and_id_is_given @pirate.delete @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye') - assert_no_difference('Pirate.count') do - @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id }) - end + @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id }) + assert_equal 'Arr', @pirate.reload.catchphrase + assert_equal @pirate, @ship.reload.update_only_pirate end def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction @@ -451,9 +457,10 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase @pirate.delete @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye') - assert_difference('Pirate.count', -1) do - @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id, :_destroy => true }) - end + @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id, :_destroy => true }) + + assert_raise(ActiveRecord::RecordNotFound) { @pirate.reload } + Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => false end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index aa75aa27d4..be038bfa74 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -521,7 +521,7 @@ class RelationTest < ActiveRecord::TestCase posts = Post.scoped assert_equal [0], posts.select('comments_count').where('id is not null').group('id').order('id').count.values.uniq - assert_equal 7, posts.where('id is not null').select('comments_count').count + assert_equal 0, posts.where('id is not null').select('comments_count').count assert_equal 7, posts.select('comments_count').count('id') assert_equal 0, posts.select('comments_count').count diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 7657e00800..ea62833d81 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -233,7 +233,7 @@ ActiveRecord::Schema.define do end create_table :items, :force => true do |t| - t.column :name, :integer + t.column :name, :string end create_table :inept_wizards, :force => true do |t| @@ -343,8 +343,8 @@ ActiveRecord::Schema.define do t.decimal :my_house_population, :precision => 2, :scale => 0 t.decimal :decimal_number_with_default, :precision => 3, :scale => 2, :default => 2.78 t.float :temperature - # Oracle supports precision up to 38 - if current_adapter?(:OracleAdapter) + # Oracle/SQLServer supports precision up to 38 + if current_adapter?(:OracleAdapter,:SQLServerAdapter) t.decimal :atoms_in_universe, :precision => 38, :scale => 0 else t.decimal :atoms_in_universe, :precision => 55, :scale => 0 diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb index 91b375681b..77135be146 100644 --- a/activeresource/test/cases/base_test.rb +++ b/activeresource/test/cases/base_test.rb @@ -1041,6 +1041,12 @@ class BaseTest < Test::Unit::TestCase ensure Person.element_name = old_elem_name end + + def test_to_xml_with_private_method_name_as_attribute + assert_nothing_raised(ArgumentError) { + Customer.new(:test => true).to_xml + } + end def test_to_json Person.include_root_in_json = true diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 84cdc22e40..383cdbb52f 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,6 +1,6 @@ *Rails 3.1.0 (unreleased)* -* No changes +* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White] *Rails 3.0.0 (August 29, 2010)* diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 40a1866428..3a7652f5bf 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -113,7 +113,7 @@ class Module raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." end - prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_" + prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_" || '' file, line = caller.first.split(':', 2) line = line.to_i diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index e6170b2daf..4bd97d3ee3 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -511,7 +511,12 @@ module ActiveSupport #:nodoc: end # Remove the constants that have been autoloaded, and those that have been - # marked for unloading. + # marked for unloading. Before each constant is removed a callback is sent + # to its class/module if it implements +before_remove_const+. + # + # The callback implementation should be restricted to cleaning up caches, etc. + # as the enviroment will be in an inconsistent state, e.g. other constants + # may have already been unloaded and not accessible. def remove_unloadable_constants! autoloaded_constants.each { |const| remove_constant const } autoloaded_constants.clear @@ -636,6 +641,7 @@ module ActiveSupport #:nodoc: parent = Inflector.constantize(names * '::') log "removing constant #{const}" + constantize(const).before_remove_const if constantize(const).respond_to?(:before_remove_const) parent.instance_eval { remove_const to_remove } return true diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index a535e2b668..de3ded1e1f 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -99,7 +99,7 @@ module ActiveSupport private def method_missing(method, *args, &block) #:nodoc: - value.send(method, *args) + value.send(method, *args, &block) end end end diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 8469f78566..b6456f0a30 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -129,6 +129,14 @@ class DurationTest < ActiveSupport::TestCase assert_equal Time.local(2009,3,29,0,0,0) + 1.day, Time.local(2009,3,30,0,0,0) end end + + def test_delegation_with_block_works + counter = 0 + assert_nothing_raised do + 1.minute.times {counter += 1} + end + assert_equal counter, 60 + end protected def with_env_tz(new_tz = 'US/Eastern') diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 5d9cdf22c2..75404ec0e1 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -47,6 +47,14 @@ Project = Struct.new(:description, :person) do delegate :to_f, :to => :description, :allow_nil => true end +Developer = Struct.new(:client) do + delegate :name, :to => :client, :prefix => nil +end + +Tester = Struct.new(:client) do + delegate :name, :to => :client, :prefix => false +end + class Name delegate :upcase, :to => :@full_name @@ -97,6 +105,11 @@ class ModuleTest < Test::Unit::TestCase assert_equal invoice.customer_city, "Chicago" end + def test_delegation_prefix_with_nil_or_false + assert_equal Developer.new(@david).name, "David" + assert_equal Tester.new(@david).name, "David" + end + def test_delegation_prefix_with_instance_variable assert_raise ArgumentError do Class.new do diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 77b885dc3d..bc7f597f1d 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -574,6 +574,17 @@ class DependenciesTest < Test::Unit::TestCase end end + def test_unloadable_constants_should_receive_callback + Object.const_set :C, Class.new + C.unloadable + C.expects(:before_remove_const).once + assert C.respond_to?(:before_remove_const) + ActiveSupport::Dependencies.clear + assert !defined?(C) + ensure + Object.class_eval { remove_const :C } if defined?(C) + end + def test_new_contants_in_without_constants assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) { }) assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 855467227b..8fd2aa0bce 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -46,6 +46,13 @@ module Rails ActiveSupport.run_load_hooks(:after_initialize, self) end + # Force routes to be loaded just at the end and add it to to_prepare callbacks + initializer :set_routes_reloader do |app| + reloader = lambda { app.routes_reloader.execute_if_updated } + reloader.call + ActionDispatch::Callbacks.to_prepare(&reloader) + end + # Disable dependency loading during request cycle initializer :disable_dependency_loading do if config.cache_classes && !config.dependency_loading diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index 834a120c01..de2f190ad5 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -48,5 +48,5 @@ end # Has to set the RAILS_ENV before config/application is required if ARGV.first && !ARGV.first.index("-") && env = ARGV.pop # has to pop the env ARGV so IRB doesn't freak - ENV['RAILS_ENV'] = %w(production development test).find { |e| e.index(env) } || env + ENV['RAILS_ENV'] = %w(production development test).detect {|e| e =~ /^#{env}/} || env end diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 3b9fff2f4a..64273e4ca4 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -6,6 +6,7 @@ module Rails attr_accessor :name, :type def initialize(name, type) + raise Thor::Error, "Missing type for attribute '#{name}'.\nExample: '#{name}:string' where string is the type." if type.blank? @name, @type = name, type.to_sym end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 3a8a63a2f9..d553c09484 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -23,8 +23,9 @@ gem '<%= gem_for_database %>'<% if require_for_database %>, :require => '<%= req # Deploy with Capistrano # gem 'capistrano' -# To use debugger +# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) # gem 'ruby-debug' +# gem 'ruby-debug19' # Bundle the extra gems: # gem 'bj' diff --git a/railties/test/application/rackup_test.rb b/railties/test/application/rackup_test.rb index 863950c04f..b0a9925890 100644 --- a/railties/test/application/rackup_test.rb +++ b/railties/test/application/rackup_test.rb @@ -31,13 +31,6 @@ module ApplicationTests assert_kind_of Rails::Application, Rails.application end - # Passenger still uses AC::Dispatcher, so we need to - # keep it working for now - test "deprecated ActionController::Dispatcher still works" do - rackup - assert_kind_of Rails::Application, ActionController::Dispatcher.new - end - test "the config object is available on the application object" do rackup assert_equal 'UTC', Rails.application.config.time_zone diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index febc53bac9..53bb7868da 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -215,6 +215,23 @@ module ApplicationTests end end + test 'routes are loaded just after initialization' do + require "#{app_path}/config/application" + + ActiveSupport.on_load(:after_initialize) do + ::InitializeRackApp = lambda { |env| [200, {}, ["InitializeRackApp"]] } + end + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do |map| + match 'foo', :to => ::InitializeRackApp + end + RUBY + + get '/foo' + assert_equal "InitializeRackApp", last_response.body + end + test 'resource routing with irrigular inflection' do app_file 'config/initializers/inflection.rb', <<-RUBY ActiveSupport::Inflector.inflections do |inflect| diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index de7e4de2a6..272e179fe3 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -108,4 +108,16 @@ class GeneratedAttributeTest < Rails::Generators::TestCase ) end end + + def test_nil_type_raises_exception + assert_raise Thor::Error do + create_generated_attribute(nil, 'title') + end + end + + def test_missing_type_raises_exception + assert_raise Thor::Error do + create_generated_attribute(:'', 'title') + end + end end diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index ef415a4fed..f4a9a152c9 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -11,6 +11,12 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_match /TestUnit options:/, content end + def test_model_with_missing_attribute_type + content = capture(:stderr) { run_generator ["post", "title:string", "body"] } + assert_match /Missing type for attribute 'body'/, content + assert_match /Example: 'body:string' where string is the type/, content + end + def test_invokes_default_orm run_generator assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/ diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index f12445ae35..446bed3269 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -231,4 +231,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/ end + + def test_scaffold_generator_outputs_error_message_on_missing_attribute_type + content = capture(:stderr) { run_generator ["post", "title:string", "body"]} + assert_match /Missing type for attribute 'body'/, content + assert_match /Example: 'body:string' where string is the type/, content + end end |