aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionmailer/lib/action_mailer/base.rb3
-rw-r--r--actionmailer/test/abstract_unit.rb2
-rw-r--r--actionmailer/test/fixtures/test_mailer/body_ivar.erb2
-rw-r--r--actionmailer/test/mail_service_test.rb12
-rw-r--r--actionpack/lib/action_controller/base.rb19
-rw-r--r--actionpack/lib/action_controller/dispatcher.rb30
-rw-r--r--actionpack/lib/action_controller/integration.rb2
-rwxr-xr-xactionpack/lib/action_controller/request.rb5
-rw-r--r--actionpack/lib/action_controller/rescue.rb130
-rw-r--r--actionpack/lib/action_controller/test_case.rb2
-rw-r--r--actionpack/lib/action_view/base.rb18
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb10
-rw-r--r--actionpack/test/abstract_unit.rb2
-rw-r--r--actionpack/test/controller/render_test.rb41
-rw-r--r--actionpack/test/controller/rescue_test.rb35
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb6
-rw-r--r--activerecord/lib/active_record/association_preload.rb32
-rwxr-xr-xactiverecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb11
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb4
-rwxr-xr-xactiverecord/lib/active_record/base.rb14
-rw-r--r--activerecord/lib/active_record/calculations.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb6
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb8
-rw-r--r--activerecord/lib/active_record/reflection.rb135
-rw-r--r--activerecord/test/cases/active_schema_test_mysql.rb5
-rw-r--r--activerecord/test/cases/associations/eager_test.rb16
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb1
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb17
-rw-r--r--activerecord/test/cases/calculations_test.rb6
-rw-r--r--activerecord/test/cases/finder_test.rb27
-rw-r--r--activerecord/test/cases/reflection_test.rb4
-rw-r--r--activerecord/test/cases/reload_models_test.rb20
-rw-r--r--activerecord/test/models/post.rb2
-rw-r--r--activeresource/lib/active_resource/base.rb3
-rw-r--r--activeresource/test/base_test.rb24
-rw-r--r--activesupport/CHANGELOG2
-rw-r--r--activesupport/lib/active_support.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/extending.rb45
-rw-r--r--activesupport/lib/active_support/rescuable.rb108
-rw-r--r--activesupport/lib/active_support/secure_random.rb4
-rw-r--r--activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb11
-rw-r--r--activesupport/test/rescuable_test.rb75
-rw-r--r--activesupport/test/secure_random_test.rb4
-rw-r--r--activesupport/test/test_test.rb18
-rw-r--r--railties/configs/databases/ibm_db.yml62
-rw-r--r--railties/doc/guides/actionview/layouts_and_rendering.txt28
-rw-r--r--railties/doc/guides/activerecord/association_basics.txt2
-rw-r--r--railties/doc/guides/routing/routing_outside_in.txt17
-rw-r--r--railties/lib/initializer.rb2
-rw-r--r--railties/lib/rails/gem_builder.rb6
-rw-r--r--railties/lib/rails/gem_dependency.rb142
-rw-r--r--railties/lib/rails/plugin.rb2
-rw-r--r--railties/lib/rails/vendor_gem_source_index.rb93
-rw-r--r--railties/lib/rails_generator/generators/applications/app/app_generator.rb2
-rw-r--r--railties/lib/tasks/databases.rake2
-rw-r--r--railties/lib/tasks/gems.rake20
-rw-r--r--railties/test/gem_dependency_test.rb49
-rw-r--r--railties/test/vendor/gems/dummy-gem-a-0.4.0/.specification28
-rw-r--r--railties/test/vendor/gems/dummy-gem-a-0.4.0/lib/dummy-gem-a.rb1
-rw-r--r--railties/test/vendor/gems/dummy-gem-b-0.4.0/.specification28
-rw-r--r--railties/test/vendor/gems/dummy-gem-b-0.4.0/lib/dummy-gem-b.rb1
-rw-r--r--railties/test/vendor/gems/dummy-gem-b-0.6.0/.specification28
-rw-r--r--railties/test/vendor/gems/dummy-gem-b-0.6.0/lib/dummy-gem-b.rb1
-rw-r--r--railties/test/vendor/gems/dummy-gem-c-0.4.0/.specification28
-rw-r--r--railties/test/vendor/gems/dummy-gem-c-0.4.0/lib/dummy-gem-c.rb1
-rw-r--r--railties/test/vendor/gems/dummy-gem-c-0.6.0/.specification28
-rw-r--r--railties/test/vendor/gems/dummy-gem-c-0.6.0/lib/dummy-gem-c.rb1
70 files changed, 1187 insertions, 355 deletions
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index bfe435550b..043f56ba17 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -296,6 +296,9 @@ module ActionMailer #:nodoc:
@@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ]
cattr_accessor :default_implicit_parts_order
+ cattr_reader :protected_instance_variables
+ @@protected_instance_variables = %w(@body)
+
# Specify the BCC addresses for the message
adv_attr_accessor :bcc
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 107b2e8bbe..905f25c9f7 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -1,6 +1,8 @@
require 'test/unit'
$:.unshift "#{File.dirname(__FILE__)}/../lib"
+$:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
+$:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib"
require 'action_mailer'
require 'action_mailer/test_case'
diff --git a/actionmailer/test/fixtures/test_mailer/body_ivar.erb b/actionmailer/test/fixtures/test_mailer/body_ivar.erb
new file mode 100644
index 0000000000..1421e5c908
--- /dev/null
+++ b/actionmailer/test/fixtures/test_mailer/body_ivar.erb
@@ -0,0 +1,2 @@
+body: <%= @body %>
+bar: <%= @bar %> \ No newline at end of file
diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb
index f57c6f3fb8..7f9540c44b 100644
--- a/actionmailer/test/mail_service_test.rb
+++ b/actionmailer/test/mail_service_test.rb
@@ -273,6 +273,13 @@ class TestMailer < ActionMailer::Base
headers "return-path" => "another@somewhere.test"
end
+ def body_ivar(recipient)
+ recipients recipient
+ subject "Body as a local variable"
+ from "test@example.com"
+ body :body => "foo", :bar => "baz"
+ end
+
class <<self
attr_accessor :received_body
end
@@ -926,6 +933,11 @@ EOF
TestMailer.deliver_return_path
assert_match %r{^Return-Path: <another@somewhere.test>}, MockSMTP.deliveries[0][0]
end
+
+ def test_body_is_stored_as_an_ivar
+ mail = TestMailer.create_body_ivar(@recipient)
+ assert_equal "body: foo\nbar: baz", mail.body
+ end
end
end # uses_mocha
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 91f531f12c..413f6d48e5 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -290,8 +290,6 @@ module ActionController #:nodoc:
@@allow_concurrency = false
cattr_accessor :allow_concurrency
- @@guard = Monitor.new
-
# Modern REST web services often need to submit complex data to the web application.
# The <tt>@@param_parsers</tt> hash lets you register handlers which will process the HTTP body and add parameters to the
# <tt>params</tt> hash. These handlers are invoked for POST and PUT requests.
@@ -532,12 +530,7 @@ module ActionController #:nodoc:
assign_names
log_processing
-
- if @@allow_concurrency
- send(method, *arguments)
- else
- @@guard.synchronize { send(method, *arguments) }
- end
+ send(method, *arguments)
send_response
ensure
@@ -975,13 +968,15 @@ module ActionController #:nodoc:
# Sets the Last-Modified response header. Returns 304 Not Modified if the
# If-Modified-Since request header is <= last modified.
def last_modified!(utc_time)
- head(:not_modified) if response.last_modified!(utc_time)
+ response.last_modified= utc_time
+ head(:not_modified) if response.last_modified == request.if_modified_since
end
# Sets the ETag response header. Returns 304 Not Modified if the
# If-None-Match request header matches.
def etag!(etag)
- head(:not_modified) if response.etag!(etag)
+ response.etag = etag
+ head(:not_modified) if response.etag == request.if_none_match
end
# Clears the rendered results, allowing for another render to be performed.
@@ -1256,7 +1251,7 @@ module ActionController #:nodoc:
action_name = strip_out_controller(action_name)
end
end
- "#{self.class.controller_path}/#{action_name}"
+ "#{self.controller_path}/#{action_name}"
end
def strip_out_controller(path)
@@ -1264,7 +1259,7 @@ module ActionController #:nodoc:
end
def template_path_includes_controller?(path)
- self.class.controller_path.split('/')[-1] == path.split('/')[0]
+ self.controller_path.split('/')[-1] == path.split('/')[0]
end
def process_cleanup
diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb
index bdae5f9d86..90c8400c11 100644
--- a/actionpack/lib/action_controller/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatcher.rb
@@ -2,6 +2,8 @@ module ActionController
# Dispatches requests to the appropriate controller and takes care of
# reloading the app after each request when Dependencies.load? is true.
class Dispatcher
+ @@guard = Mutex.new
+
class << self
def define_dispatcher_callbacks(cache_classes)
unless cache_classes
@@ -20,6 +22,7 @@ module ActionController
end
if defined?(ActiveRecord)
+ after_dispatch :checkin_connections
before_dispatch { ActiveRecord::Base.verify_active_connections! }
to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers }
end
@@ -98,7 +101,7 @@ module ActionController
@output, @request, @response = output, request, response
end
- def dispatch
+ def dispatch_unlocked
begin
run_callbacks :before_dispatch
handle_request
@@ -109,6 +112,16 @@ module ActionController
end
end
+ def dispatch
+ if ActionController::Base.allow_concurrency
+ dispatch_unlocked
+ else
+ @@guard.synchronize do
+ dispatch_unlocked
+ end
+ end
+ end
+
def dispatch_cgi(cgi, session_options)
if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new }
@request = CgiRequest.new(cgi, session_options)
@@ -145,6 +158,21 @@ module ActionController
Base.logger.flush
end
+ def mark_as_test_request!
+ @test_request = true
+ self
+ end
+
+ def test_request?
+ @test_request
+ end
+
+ def checkin_connections
+ # Don't return connection (and peform implicit rollback) if this request is a part of integration test
+ return if test_request?
+ ActiveRecord::Base.clear_active_connections!
+ end
+
protected
def handle_request
@controller = Routing::Routes.recognize(@request)
diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb
index a98c1af7f9..fc473c269c 100644
--- a/actionpack/lib/action_controller/integration.rb
+++ b/actionpack/lib/action_controller/integration.rb
@@ -276,7 +276,7 @@ module ActionController
ActionController::Base.clear_last_instantiation!
env['rack.input'] = data.is_a?(IO) ? data : StringIO.new(data || '')
- @status, @headers, result_body = ActionController::Dispatcher.new.call(env)
+ @status, @headers, result_body = ActionController::Dispatcher.new.mark_as_test_request!.call(env)
@request_count += 1
@controller = ActionController::Base.last_instantiation
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index 8e6cfb41dc..5e492e3ee1 100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -167,7 +167,7 @@ module ActionController
parameter_format = parameters[:format]
if parameter_format
- parameter_format.to_sym
+ parameter_format
elsif xhr?
:js
else
@@ -176,8 +176,7 @@ module ActionController
end
def cache_format
- parameter_format = parameters[:format]
- parameter_format && parameter_format.to_sym
+ parameters[:format]
end
# Returns true if the request's "X-Requested-With" header contains
diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb
index 83c4218af4..ec8e9b92d5 100644
--- a/actionpack/lib/action_controller/rescue.rb
+++ b/actionpack/lib/action_controller/rescue.rb
@@ -41,10 +41,9 @@ module ActionController #:nodoc:
base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
- base.class_inheritable_array :rescue_handlers
- base.rescue_handlers = []
-
base.extend(ClassMethods)
+ base.send :include, ActiveSupport::Rescuable
+
base.class_eval do
alias_method_chain :perform_action, :rescue
end
@@ -54,82 +53,12 @@ module ActionController #:nodoc:
def process_with_exception(request, response, exception) #:nodoc:
new.process(request, response, :rescue_action, exception)
end
-
- # Rescue exceptions raised in controller actions.
- #
- # <tt>rescue_from</tt> receives a series of exception classes or class
- # names, and a trailing <tt>:with</tt> option with the name of a method
- # or a Proc object to be called to handle them. Alternatively a block can
- # be given.
- #
- # Handlers that take one argument will be called with the exception, so
- # that the exception can be inspected when dealing with it.
- #
- # Handlers are inherited. They are searched from right to left, from
- # bottom to top, and up the hierarchy. The handler of the first class for
- # which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
- # any.
- #
- # class ApplicationController < ActionController::Base
- # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
- # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
- #
- # rescue_from 'MyAppError::Base' do |exception|
- # render :xml => exception, :status => 500
- # end
- #
- # protected
- # def deny_access
- # ...
- # end
- #
- # def show_errors(exception)
- # exception.record.new_record? ? ...
- # end
- # end
- def rescue_from(*klasses, &block)
- options = klasses.extract_options!
- unless options.has_key?(:with)
- block_given? ? options[:with] = block : raise(ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument.")
- end
-
- klasses.each do |klass|
- key = if klass.is_a?(Class) && klass <= Exception
- klass.name
- elsif klass.is_a?(String)
- klass
- else
- raise(ArgumentError, "#{klass} is neither an Exception nor a String")
- end
-
- # Order is important, we put the pair at the end. When dealing with an
- # exception we will follow the documented order going from right to left.
- rescue_handlers << [key, options[:with]]
- end
- end
end
protected
# Exception handler called when the performance of an action raises an exception.
def rescue_action(exception)
- if handler_for_rescue(exception)
- rescue_action_with_handler(exception)
- else
- log_error(exception) if logger
- erase_results if performed?
-
- # Let the exception alter the response if it wants.
- # For example, MethodNotAllowed sets the Allow header.
- if exception.respond_to?(:handle_response!)
- exception.handle_response!(response)
- end
-
- if consider_all_requests_local || local_request?
- rescue_action_locally(exception)
- else
- rescue_action_in_public(exception)
- end
- end
+ rescue_with_handler(exception) || rescue_action_without_handler(exception)
end
# Overwrite to implement custom logging of errors. By default logs as fatal.
@@ -185,15 +114,20 @@ module ActionController #:nodoc:
render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
end
- # Tries to rescue the exception by looking up and calling a registered handler.
- def rescue_action_with_handler(exception)
- if handler = handler_for_rescue(exception)
- if handler.arity != 0
- handler.call(exception)
- else
- handler.call
- end
- true # don't rely on the return value of the handler
+ def rescue_action_without_handler(exception)
+ log_error(exception) if logger
+ erase_results if performed?
+
+ # Let the exception alter the response if it wants.
+ # For example, MethodNotAllowed sets the Allow header.
+ if exception.respond_to?(:handle_response!)
+ exception.handle_response!(response)
+ end
+
+ if consider_all_requests_local || local_request?
+ rescue_action_locally(exception)
+ else
+ rescue_action_in_public(exception)
end
end
@@ -216,36 +150,6 @@ module ActionController #:nodoc:
rescue_responses[exception.class.name]
end
- def handler_for_rescue(exception)
- # We go from right to left because pairs are pushed onto rescue_handlers
- # as rescue_from declarations are found.
- _, handler = *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.
- #
- # Since this loop needs the constants it would be inconsistent to
- # assume they should exist at this point. An early raised exception
- # could trigger some other handler and the array could include
- # precisely a string whose corresponding constant has not yet been
- # seen. This is why we are tolerant to unknown constants.
- #
- # Note that this tolerance only matters if the exception was given as
- # a string, otherwise a NameError will be raised by the interpreter
- # itself when rescue_from CONSTANT is executed.
- klass = self.class.const_get(klass_name) rescue nil
- klass ||= klass_name.constantize rescue nil
- exception.is_a?(klass) if klass
- end
-
- case handler
- when Symbol
- method(handler)
- when Proc
- handler.bind(self)
- end
- end
-
def clean_backtrace(exception)
if backtrace = exception.backtrace
if defined?(RAILS_ROOT)
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 3e66947d5f..6a39039504 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -84,7 +84,7 @@ module ActionController
module RaiseActionExceptions
attr_accessor :exception
- def rescue_action(e)
+ def rescue_action_without_handler(e)
self.exception = e
if request.remote_addr == "0.0.0.0"
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 8c00670087..8df10c40cc 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -290,21 +290,23 @@ module ActionView #:nodoc:
private
attr_accessor :_first_render, :_last_render
- # Evaluate the local assigns and pushes them to the view.
+ # Evaluates the local assigns and controller ivars, pushes them to the view.
def _evaluate_assigns_and_ivars #:nodoc:
unless @assigns_added
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
-
- if @controller
- variables = @controller.instance_variables
- variables -= @controller.protected_instance_variables if @controller.respond_to?(:protected_instance_variables)
- variables.each {|name| instance_variable_set(name, @controller.instance_variable_get(name)) }
- end
-
+ _copy_ivars_from_controller
@assigns_added = true
end
end
+ def _copy_ivars_from_controller #:nodoc:
+ if @controller
+ variables = @controller.instance_variable_names
+ variables -= @controller.protected_instance_variables if @controller.respond_to?(:protected_instance_variables)
+ variables.each { |name| instance_variable_set(name, @controller.instance_variable_get(name)) }
+ end
+ end
+
def _set_controller_content_type(content_type) #:nodoc:
if controller.respond_to?(:response)
controller.response.content_type ||= content_type
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 294c22521e..208bf91dd4 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -403,6 +403,7 @@ module ActionView
# Creates a field set for grouping HTML form elements.
#
# <tt>legend</tt> will become the fieldset's title (optional as per W3C).
+ # <tt>options</tt> accept the same values as tag.
#
# === Examples
# <% field_set_tag do %>
@@ -414,9 +415,14 @@ module ActionView
# <p><%= text_field_tag 'name' %></p>
# <% end %>
# # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset>
- def field_set_tag(legend = nil, &block)
+ #
+ # <% field_set_tag nil, :class => 'format' do %>
+ # <p><%= text_field_tag 'name' %></p>
+ # <% end %>
+ # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
+ def field_set_tag(legend = nil, options = nil, &block)
content = capture(&block)
- concat(tag(:fieldset, {}, true))
+ concat(tag(:fieldset, options, true))
concat(content_tag(:legend, legend)) unless legend.blank?
concat(content)
concat("</fieldset>")
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 9db4cddd6a..673efa6af0 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -1,5 +1,5 @@
$:.unshift(File.dirname(__FILE__) + '/../lib')
-$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib/active_support')
+$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
require 'yaml'
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index af7b5dde62..5a6ca98b2e 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -39,6 +39,16 @@ class TestController < ActionController::Base
render :action => 'hello_world'
end
end
+
+ def conditional_hello_with_bangs
+ render :action => 'hello_world'
+ end
+ before_filter :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs
+
+ def handle_last_modified_and_etags
+ last_modified! Time.now.utc.beginning_of_day
+ etag! [:foo, 123]
+ end
def render_hello_world
render :template => "test/hello_world"
@@ -1306,6 +1316,7 @@ class EtagRenderTest < Test::Unit::TestCase
@controller = TestController.new
@request.host = "www.nextangle.com"
+ @expected_bang_etag = etag_for(expand_key([:foo, 123]))
end
def test_render_200_should_set_etag
@@ -1365,11 +1376,27 @@ class EtagRenderTest < Test::Unit::TestCase
assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
assert_equal etag_for("<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n"), @response.headers['ETag']
end
-
+
+ def test_etag_with_bang_should_set_etag
+ get :conditional_hello_with_bangs
+ assert_equal @expected_bang_etag, @response.headers["ETag"]
+ assert_response :success
+ end
+
+ def test_etag_with_bang_should_obey_if_none_match
+ @request.if_none_match = @expected_bang_etag
+ get :conditional_hello_with_bangs
+ assert_response :not_modified
+ end
+
protected
def etag_for(text)
%("#{Digest::MD5.hexdigest(text)}")
end
+
+ def expand_key(args)
+ ActiveSupport::Cache.expand_cache_key(args)
+ end
end
class LastModifiedRenderTest < Test::Unit::TestCase
@@ -1402,6 +1429,18 @@ class LastModifiedRenderTest < Test::Unit::TestCase
assert !@response.body.blank?
assert_equal @last_modified, @response.headers['Last-Modified']
end
+
+ def test_request_with_bang_gets_last_modified
+ get :conditional_hello_with_bangs
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ assert_response :success
+ end
+
+ def test_request_with_bang_obeys_last_modified
+ @request.if_modified_since = @last_modified
+ get :conditional_hello_with_bangs
+ assert_response :not_modified
+ end
end
class RenderingLoggingTest < Test::Unit::TestCase
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index da076d2090..32c6c013f1 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -75,7 +75,7 @@ class RescueController < ActionController::Base
def method_not_allowed
raise ActionController::MethodNotAllowed.new(:get, :head, :put)
end
-
+
def not_implemented
raise ActionController::NotImplemented.new(:get, :put)
end
@@ -107,7 +107,7 @@ class RescueController < ActionController::Base
def record_invalid_raise_as_string
raise RecordInvalidToRescueAsString
end
-
+
def bad_gateway
raise BadGateway
end
@@ -135,18 +135,19 @@ class RescueController < ActionController::Base
end
end
-class RescueTest < Test::Unit::TestCase
+class RescueControllerTest < ActionController::TestCase
FIXTURE_PUBLIC = "#{File.dirname(__FILE__)}/../fixtures".freeze
- def setup
- @controller = RescueController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
+ setup :set_all_requests_local
+ setup :populate_exception_object
+ def set_all_requests_local
RescueController.consider_all_requests_local = true
@request.remote_addr = '1.2.3.4'
@request.host = 'example.com'
+ end
+ def populate_exception_object
begin
raise 'foo'
rescue => @exception
@@ -307,7 +308,7 @@ class RescueTest < Test::Unit::TestCase
assert_nil @controller.send(:clean_backtrace, Exception.new)
end
end
-
+
def test_not_implemented
with_all_requests_local false do
with_rails_public_path(".") do
@@ -463,14 +464,7 @@ class ExceptionInheritanceRescueController < ActionController::Base
end
end
-class ExceptionInheritanceRescueTest < Test::Unit::TestCase
-
- def setup
- @controller = ExceptionInheritanceRescueController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
- end
-
+class ExceptionInheritanceRescueControllerTest < ActionController::TestCase
def test_bottom_first
get :raise_grandchild_exception
assert_response :no_content
@@ -500,14 +494,7 @@ class ControllerInheritanceRescueController < ExceptionInheritanceRescueControll
end
end
-class ControllerInheritanceRescueControllerTest < Test::Unit::TestCase
-
- def setup
- @controller = ControllerInheritanceRescueController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
- end
-
+class ControllerInheritanceRescueControllerTest < ActionController::TestCase
def test_first_exception_in_child_controller
get :raise_first_exception_in_child_controller
assert_response :gone
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 6473d011d5..ad8baef5e4 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -271,6 +271,12 @@ class FormTagHelperTest < ActionView::TestCase
expected = %(<fieldset>Hello world!</fieldset>)
assert_dom_equal expected, output_buffer
+
+ self.output_buffer = ''
+ field_set_tag('', :class => 'format') { concat "Hello world!" }
+
+ expected = %(<fieldset class="format">Hello world!</fieldset>)
+ assert_dom_equal expected, output_buffer
end
def protect_against_forgery?
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index cef8cd8647..99e3973830 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -161,12 +161,13 @@ module ActiveRecord
# the objects' IDs to the relevant objects. Returns a 2-tuple
# <tt>(id_to_record_map, ids)</tt> where +id_to_record_map+ is the Hash,
# and +ids+ is an Array of record IDs.
- def construct_id_map(records)
+ def construct_id_map(records, primary_key=nil)
id_to_record_map = {}
ids = []
records.each do |record|
- ids << record.id
- mapped_records = (id_to_record_map[record.id.to_s] ||= [])
+ primary_key ||= record.class.primary_key
+ ids << record[primary_key]
+ mapped_records = (id_to_record_map[ids.last.to_s] ||= [])
mapped_records << record
end
ids.uniq!
@@ -180,7 +181,7 @@ module ActiveRecord
options = reflection.options
conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
- conditions << append_conditions(options, preload_options)
+ conditions << append_conditions(reflection, preload_options)
associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
:include => options[:include],
@@ -213,23 +214,24 @@ module ActiveRecord
end
def preload_has_many_association(records, reflection, preload_options={})
- id_to_record_map, ids = construct_id_map(records)
- records.each {|record| record.send(reflection.name).loaded}
options = reflection.options
+ primary_key_name = reflection.through_reflection_primary_key_name
+ id_to_record_map, ids = construct_id_map(records, primary_key_name)
+ records.each {|record| record.send(reflection.name).loaded}
+
if options[:through]
through_records = preload_through_records(records, reflection, options[:through])
through_reflection = reflections[options[:through]]
- through_primary_key = through_reflection.primary_key_name
unless through_records.empty?
source = reflection.source_reflection.name
- #add conditions from reflection!
- through_records.first.class.preload_associations(through_records, source, reflection.options)
+ through_records.first.class.preload_associations(through_records, source, options)
through_records.each do |through_record|
- add_preloaded_records_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
- reflection.name, through_record.send(source))
+ through_record_id = through_record[reflection.through_reflection_primary_key].to_s
+ add_preloaded_records_to_collection(id_to_record_map[through_record_id], reflection.name, through_record.send(source))
end
end
+
else
set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options),
reflection.primary_key_name)
@@ -317,7 +319,7 @@ module ActiveRecord
end
end
conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
- conditions << append_conditions(options, preload_options)
+ conditions << append_conditions(reflection, preload_options)
associated_records = klass.find(:all, :conditions => [conditions, ids],
:include => options[:include],
:select => options[:select],
@@ -338,7 +340,7 @@ module ActiveRecord
conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}"
end
- conditions << append_conditions(options, preload_options)
+ conditions << append_conditions(reflection, preload_options)
reflection.klass.find(:all,
:select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
@@ -354,9 +356,9 @@ module ActiveRecord
instance_eval("%@#{sql.gsub('@', '\@')}@")
end
- def append_conditions(options, preload_options)
+ def append_conditions(reflection, preload_options)
sql = ""
- sql << " AND (#{interpolate_sql_for_preload(sanitize_sql(options[:conditions]))})" if options[:conditions]
+ sql << " AND (#{interpolate_sql_for_preload(reflection.sanitized_conditions)})" if reflection.sanitized_conditions
sql << " AND (#{sanitize_sql preload_options[:conditions]})" if preload_options[:conditions]
sql
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 4d36216c34..d1a0b2f96a 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -955,8 +955,6 @@ module ActiveRecord
# destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
# is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing
# a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
- # When creating a counter cache column, the database statement or migration must specify a default value of <tt>0</tt>, failing to do
- # this results in a counter with +NULL+ value, which will never increment.
# Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
# [:include]
# Specify second-order associations that should be eager loaded when this object is loaded.
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index acdcd14ec8..b617147f67 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -240,7 +240,7 @@ module ActiveRecord
# the kind of the class of the associated objects. Meant to be used as
# a sanity check when you are about to assign an associated record.
def raise_on_type_mismatch(record)
- unless record.is_a?(@reflection.klass)
+ unless record.is_a?(@reflection.klass) || record.is_a?(@reflection.class_name.constantize)
message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 3171665e19..a0bb3a45b0 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -32,6 +32,14 @@ module ActiveRecord
end
protected
+ def target_reflection_has_associated_record?
+ if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
+ false
+ else
+ true
+ end
+ end
+
def construct_find_options!(options)
options[:select] = construct_select(options[:select])
options[:from] ||= construct_from
@@ -61,6 +69,7 @@ module ActiveRecord
end
def find_target
+ return [] unless target_reflection_has_associated_record?
@reflection.klass.find(:all,
:select => construct_select,
:conditions => construct_conditions,
@@ -102,6 +111,8 @@ module ActiveRecord
"#{as}_type" => reflection.klass.quote_value(
@owner.class.base_class.name.to_s,
reflection.klass.columns_hash["#{as}_type"]) }
+ elsif reflection.macro == :belongs_to
+ { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
else
{ reflection.primary_key_name => owner_quoted_id }
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index e5486738f0..1c753524de 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -342,7 +342,9 @@ module ActiveRecord
method_name = method.to_s
if super
return true
- elsif self.private_methods.include?(method_name) && !include_private_methods
+ elsif !include_private_methods && super(method, true)
+ # If we're here than we haven't found among non-private methods
+ # but found among all methods. Which means that given method is private.
return false
elsif !self.class.generated_methods?
self.class.define_attribute_methods
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index ac15eed408..6a1a3794a2 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -610,8 +610,8 @@ module ActiveRecord #:nodoc:
# Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
- # this method from. If you call +Product.find_by_sql+ then the results will be returned in a Product
- # object with the attributes you specified in the SQL query.
+ # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
+ # a Product object with the attributes you specified in the SQL query.
#
# If you call a complicated SQL query which spans multiple tables the columns specified by the
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
@@ -620,7 +620,7 @@ module ActiveRecord #:nodoc:
# The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
# no database agnostic conversions performed. This should be a last resort because using, for example,
# MySQL specific terms will lock you to using that particular database engine or require you to
- # change your call if you switch engines
+ # change your call if you switch engines.
#
# ==== Examples
# # A simple SQL query spanning multiple tables
@@ -1780,10 +1780,10 @@ module ActiveRecord #:nodoc:
#{'result = ' if bang}if options[:conditions]
with_scope(:find => finder_options) do
- ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
+ find(:#{finder}, options)
end
else
- ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
+ find(:#{finder}, options.merge(finder_options))
end
#{'result || raise(RecordNotFound)' if bang}
end
@@ -1806,9 +1806,9 @@ module ActiveRecord #:nodoc:
options = { :conditions => find_attributes }
set_readonly_option!(options)
- record = find_initial(options)
+ record = find(:first, options)
- if record.nil?
+ if record.nil?
record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
#{'yield(record) if block_given?'}
#{'record.save' if instantiator == :create}
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 80992dd34b..5e33cf1bd4 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -217,7 +217,7 @@ module ActiveRecord
sql << " ORDER BY #{options[:order]} " if options[:order]
add_limit!(sql, options, scope)
- sql << ") AS #{aggregate_alias}_subquery" if use_workaround
+ sql << ") #{aggregate_alias}_subquery" if use_workaround
sql
end
@@ -285,11 +285,15 @@ module ActiveRecord
operation = operation.to_s.downcase
case operation
when 'count' then value.to_i
- when 'sum' then value =~ /\./ ? value.to_f : value.to_i
- when 'avg' then value && value.to_f
- else column ? column.type_cast(value) : value
+ when 'sum' then type_cast_using_column(value || '0', column)
+ when 'avg' then value && value.to_d
+ else type_cast_using_column(value, column)
end
end
+
+ def type_cast_using_column(value, column)
+ column ? column.type_cast(value) : value
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 10cae5d840..432c341e6c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -172,21 +172,24 @@ module ActiveRecord
# within the timeout period.
def checkout
# Checkout an available connection
- conn = @connection_mutex.synchronize do
- if @checked_out.size < @connections.size
- checkout_existing_connection
- elsif @connections.size < @size
- checkout_new_connection
- end
- end
- return conn if conn
-
- # No connections available; wait for one
@connection_mutex.synchronize do
- if @queue.wait(@timeout)
- checkout_existing_connection
- else
- raise ConnectionTimeoutError, "could not obtain a database connection within #{@timeout} seconds. The pool size is currently #{@size}, perhaps you need to increase it?"
+ loop do
+ conn = if @checked_out.size < @connections.size
+ checkout_existing_connection
+ elsif @connections.size < @size
+ checkout_new_connection
+ end
+ return conn if conn
+ # No connections available; wait for one
+ if @queue.wait(@timeout)
+ next
+ else
+ # try looting dead threads
+ clear_stale_cached_connections!
+ if @size == @checked_out.size
+ raise ConnectionTimeoutError, "could not obtain a database connection within #{@timeout} seconds. The pool size is currently #{@size}, perhaps you need to increase it?"
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index a26fd02b90..1e452ae88a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -218,7 +218,7 @@ module ActiveRecord
s = column.class.string_to_binary(value).unpack("H*")[0]
"x'#{s}'"
elsif value.kind_of?(BigDecimal)
- "'#{value.to_s("F")}'"
+ value.to_s("F")
else
super
end
@@ -371,9 +371,9 @@ module ActiveRecord
end
end
- def recreate_database(name) #:nodoc:
+ def recreate_database(name, options = {}) #:nodoc:
drop_database(name)
- create_database(name)
+ create_database(name, options)
end
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
index f4a5712981..8f9f05ce36 100644
--- a/activerecord/lib/active_record/dynamic_finder_match.rb
+++ b/activerecord/lib/active_record/dynamic_finder_match.rb
@@ -6,11 +6,11 @@ module ActiveRecord
end
def initialize(method)
- @finder = :find_initial
+ @finder = :first
case method.to_s
when /^find_(all_by|last_by|by)_([_a-zA-Z]\w*)$/
- @finder = :find_last if $1 == 'last_by'
- @finder = :find_every if $1 == 'all_by'
+ @finder = :last if $1 == 'last_by'
+ @finder = :all if $1 == 'all_by'
names = $2
when /^find_by_([_a-zA-Z]\w*)\!$/
@bang = true
@@ -31,7 +31,7 @@ module ActiveRecord
end
def instantiator?
- @finder == :find_initial && !@instantiator.nil?
+ @finder == :first && !@instantiator.nil?
end
def bang?
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index a1b498eceb..dbff4f24d6 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -13,14 +13,15 @@ module ActiveRecord
def create_reflection(macro, name, options, active_record)
case macro
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
- reflection = AssociationReflection.new(macro, name, options, active_record)
+ klass = options[:through] ? ThroughReflection : AssociationReflection
+ reflection = klass.new(macro, name, options, active_record)
when :composed_of
reflection = AggregateReflection.new(macro, name, options, active_record)
end
write_inheritable_hash :reflections, name => reflection
reflection
end
-
+
# Returns a hash containing all AssociationReflection objects for the current class
# Example:
#
@@ -30,7 +31,7 @@ module ActiveRecord
def reflections
read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
end
-
+
# Returns an array of AggregateReflection objects for all the aggregations in the class.
def reflect_on_all_aggregations
reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
@@ -116,6 +117,11 @@ module ActiveRecord
@sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
end
+ # Returns +true+ if +self+ is a +belongs_to+ reflection.
+ def belongs_to?
+ macro == :belongs_to
+ end
+
private
def derive_class_name
name.to_s.camelize
@@ -192,6 +198,52 @@ module ActiveRecord
end
end
+ def check_validity!
+ end
+
+ def through_reflection
+ false
+ end
+
+ def through_reflection_primary_key_name
+ end
+
+ def source_reflection
+ nil
+ end
+
+ private
+ def derive_class_name
+ class_name = name.to_s.camelize
+ class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
+ class_name
+ end
+
+ def derive_primary_key_name
+ if belongs_to?
+ "#{name}_id"
+ elsif options[:as]
+ "#{options[:as]}_id"
+ else
+ active_record.name.foreign_key
+ end
+ end
+ end
+
+ # Holds all the meta-data about a :through association as it was specified in the Active Record class.
+ class ThroughReflection < AssociationReflection #:nodoc:
+ # Gets the source of the through reflection. It checks both a singularized and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
+ # (The <tt>:tags</tt> association on Tagging below.)
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :taggings
+ # has_many :tags, :through => :taggings
+ # end
+ #
+ def source_reflection
+ @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
+ end
+
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
# of a HasManyThrough or HasOneThrough association. Example:
#
@@ -204,7 +256,7 @@ module ActiveRecord
# taggings_reflection = tags_reflection.through_reflection
#
def through_reflection
- @through_reflection ||= options[:through] ? active_record.reflect_on_association(options[:through]) : false
+ @through_reflection ||= active_record.reflect_on_association(options[:through])
end
# Gets an array of possible <tt>:through</tt> source reflection names:
@@ -215,63 +267,40 @@ module ActiveRecord
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
end
- # Gets the source of the through reflection. It checks both a singularized and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
- # (The <tt>:tags</tt> association on Tagging below.)
- #
- # class Post < ActiveRecord::Base
- # has_many :taggings
- # has_many :tags, :through => :taggings
- # end
- #
- def source_reflection
- return nil unless through_reflection
- @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
- end
-
def check_validity!
- if options[:through]
- if through_reflection.nil?
- raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
- end
-
- if source_reflection.nil?
- raise HasManyThroughSourceAssociationNotFoundError.new(self)
- end
+ if through_reflection.nil?
+ raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
+ end
- if options[:source_type] && source_reflection.options[:polymorphic].nil?
- raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
- end
-
- if source_reflection.options[:polymorphic] && options[:source_type].nil?
- raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
- end
-
- unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
- raise HasManyThroughSourceAssociationMacroError.new(self)
- end
+ if source_reflection.nil?
+ raise HasManyThroughSourceAssociationNotFoundError.new(self)
+ end
+
+ if options[:source_type] && source_reflection.options[:polymorphic].nil?
+ raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
end
+
+ if source_reflection.options[:polymorphic] && options[:source_type].nil?
+ raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
+ end
+
+ unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
+ raise HasManyThroughSourceAssociationMacroError.new(self)
+ end
+ end
+
+ def through_reflection_primary_key
+ through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.primary_key_name
+ end
+
+ def through_reflection_primary_key_name
+ through_reflection.primary_key_name if through_reflection.belongs_to?
end
private
def derive_class_name
# get the class_name of the belongs_to association of the through reflection
- if through_reflection
- options[:source_type] || source_reflection.class_name
- else
- class_name = name.to_s.camelize
- class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
- class_name
- end
- end
-
- def derive_primary_key_name
- if macro == :belongs_to
- "#{name}_id"
- elsif options[:as]
- "#{options[:as]}_id"
- else
- active_record.name.foreign_key
- end
+ options[:source_type] || source_reflection.class_name
end
end
end
diff --git a/activerecord/test/cases/active_schema_test_mysql.rb b/activerecord/test/cases/active_schema_test_mysql.rb
index 2a42dc3517..9aff538ce9 100644
--- a/activerecord/test/cases/active_schema_test_mysql.rb
+++ b/activerecord/test/cases/active_schema_test_mysql.rb
@@ -25,6 +25,11 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
end
+
+ def test_recreate_mysql_database_with_encoding
+ create_database(:luca, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})
+ end
end
def test_add_column
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index e78624a98d..7f42577ab0 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -116,6 +116,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 2, posts.first.comments.size
end
+ def test_loading_from_an_association_that_has_a_hash_of_conditions
+ assert_nothing_raised do
+ Author.find(:all, :include => :hello_posts_with_hash_conditions)
+ end
+ assert !Author.find(authors(:david).id, :include => :hello_posts_with_hash_conditions).hello_posts.empty?
+ end
+
def test_loading_with_no_associations
assert_nil Post.find(posts(:authorless).id, :include => :author).author
end
@@ -268,6 +275,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author }
end
+ def test_eager_with_has_many_through_a_belongs_to_association
+ author = authors(:mary)
+ post = Post.create!(:author => author, :title => "TITLE", :body => "BODY")
+ author.author_favorites.create(:favorite_author_id => 1)
+ author.author_favorites.create(:favorite_author_id => 2)
+ posts_with_author_favorites = author.posts.find(:all, :include => :author_favorites)
+ assert_no_queries { posts_with_author_favorites.first.author_favorites.first.author_id }
+ end
+
def test_eager_with_has_many_through_an_sti_join_model
author = Author.find(:first, :include => :special_post_comments, :order => 'authors.id')
assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments }
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 315d77de07..1bc9c39c19 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1081,3 +1081,4 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
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 12cce98c26..a07f4bcbdd 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -5,7 +5,7 @@ require 'models/reader'
require 'models/comment'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :posts, :readers, :people, :comments
+ fixtures :posts, :readers, :people, :comments, :authors
def test_associate_existing
assert_queries(2) { posts(:thinking);people(:david) }
@@ -229,4 +229,19 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
end
+
+ def test_has_many_association_through_a_belongs_to_association_where_the_association_doesnt_exist
+ author = authors(:mary)
+ post = Post.create!(:title => "TITLE", :body => "BODY")
+ assert_equal [], post.author_favorites
+ end
+
+ def test_has_many_association_through_a_belongs_to_association
+ author = authors(:mary)
+ post = Post.create!(:author => author, :title => "TITLE", :body => "BODY")
+ author.author_favorites.create(:favorite_author_id => 1)
+ author.author_favorites.create(:favorite_author_id => 2)
+ author.author_favorites.create(:favorite_author_id => 3)
+ assert_equal post.author.author_favorites, post.author_favorites
+ end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 754fd58f35..0fa61500c0 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -18,8 +18,8 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_average_field
value = Account.average(:credit_limit)
- assert_kind_of Float, value
- assert_in_delta 53.0, value, 0.001
+ assert_kind_of BigDecimal, value
+ assert_equal BigDecimal.new('53.0'), value
end
def test_should_return_nil_as_average
@@ -273,7 +273,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_sum_expression
- assert_equal 636, Account.sum("2 * credit_limit")
+ assert_equal '636', Account.sum("2 * credit_limit")
end
def test_count_with_from_option
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 292b88edbc..853474916c 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -21,7 +21,7 @@ class DynamicFinderMatchTest < ActiveRecord::TestCase
match = ActiveRecord::DynamicFinderMatch.match("find_by_age_and_sex_and_location")
assert_not_nil match
assert match.finder?
- assert_equal :find_initial, match.finder
+ assert_equal :first, match.finder
assert_equal %w(age sex location), match.attribute_names
end
@@ -30,7 +30,7 @@ class DynamicFinderMatchTest < ActiveRecord::TestCase
assert_not_nil match
assert match.finder?
assert match.bang?
- assert_equal :find_initial, match.finder
+ assert_equal :first, match.finder
assert_equal %w(age sex location), match.attribute_names
end
@@ -38,7 +38,7 @@ class DynamicFinderMatchTest < ActiveRecord::TestCase
match = ActiveRecord::DynamicFinderMatch.match("find_all_by_age_and_sex_and_location")
assert_not_nil match
assert match.finder?
- assert_equal :find_every, match.finder
+ assert_equal :all, match.finder
assert_equal %w(age sex location), match.attribute_names
end
@@ -47,7 +47,7 @@ class DynamicFinderMatchTest < ActiveRecord::TestCase
assert_not_nil match
assert !match.finder?
assert match.instantiator?
- assert_equal :find_initial, match.finder
+ assert_equal :first, match.finder
assert_equal :new, match.instantiator
assert_equal %w(age sex location), match.attribute_names
end
@@ -57,7 +57,7 @@ class DynamicFinderMatchTest < ActiveRecord::TestCase
assert_not_nil match
assert !match.finder?
assert match.instantiator?
- assert_equal :find_initial, match.finder
+ assert_equal :first, match.finder
assert_equal :create, match.instantiator
assert_equal %w(age sex location), match.attribute_names
end
@@ -500,6 +500,23 @@ class FinderTest < ActiveRecord::TestCase
assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
end
+ uses_mocha('test_dynamic_finder_should_go_through_the_find_class_method') do
+ def test_dynamic_finders_should_go_through_the_find_class_method
+ Topic.expects(:find).with(:first, :conditions => { :title => 'The First Topic!' })
+ Topic.find_by_title("The First Topic!")
+
+ Topic.expects(:find).with(:last, :conditions => { :title => 'The Last Topic!' })
+ Topic.find_last_by_title("The Last Topic!")
+
+ Topic.expects(:find).with(:all, :conditions => { :title => 'A Topic.' })
+ Topic.find_all_by_title("A Topic.")
+
+ Topic.expects(:find).with(:first, :conditions => { :title => 'Does not exist yet for sure!' }).times(2)
+ Topic.find_or_initialize_by_title('Does not exist yet for sure!')
+ Topic.find_or_create_by_title('Does not exist yet for sure!')
+ end
+ end
+
def test_find_by_one_attribute
assert_equal topics(:first), Topic.find_by_title("The First Topic")
assert_nil Topic.find_by_title("The First Topic!")
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index e339ef41ab..e0ed3e5886 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -170,6 +170,10 @@ class ReflectionTest < ActiveRecord::TestCase
assert_nothing_raised { Firm.reflections[:clients] == Object.new }
end
+ def test_has_many_through_reflection
+ assert_kind_of ActiveRecord::Reflection::ThroughReflection, Subscriber.reflect_on_association(:books)
+ end
+
private
def assert_reflection(klass, association, options)
assert reflection = klass.reflect_on_association(association)
diff --git a/activerecord/test/cases/reload_models_test.rb b/activerecord/test/cases/reload_models_test.rb
new file mode 100644
index 0000000000..411b5f6afa
--- /dev/null
+++ b/activerecord/test/cases/reload_models_test.rb
@@ -0,0 +1,20 @@
+require "cases/helper"
+require 'models/owner'
+require 'models/pet'
+
+class ReloadModelsTest < ActiveRecord::TestCase
+ def test_has_one_with_reload
+ pet = Pet.find_by_name('parrot')
+ pet.owner = Owner.find_by_name('ashley')
+
+ # Reload the class Owner, simulating auto-reloading of model classes in a
+ # development environment. Note that meanwhile the class Pet is not
+ # reloaded, simulating a class that is present in a plugin.
+ Object.class_eval { remove_const :Owner }
+ Kernel.load(File.expand_path(File.join(File.dirname(__FILE__), "../models/owner.rb")))
+
+ pet = Pet.find_by_name('parrot')
+ pet.owner = Owner.find_by_name('ashley')
+ assert_equal pet.owner, Owner.find_by_name('ashley')
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 3adbc0ce1f..6da37c31ff 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -22,6 +22,8 @@ class Post < ActiveRecord::Base
end
end
+ has_many :author_favorites, :through => :author
+
has_many :comments_with_interpolated_conditions, :class_name => 'Comment',
:conditions => ['#{"#{aliased_table_name}." rescue ""}body = ?', 'Thank you for the welcome']
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index d966062c7f..74d8128c0e 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -884,6 +884,7 @@ module ActiveResource
#
# ==== Examples
# my_attrs = {:name => 'J&J Textiles', :industry => 'Cloth and textiles'}
+ # my_attrs = {:name => 'Marty', :colors => ["red", "green", "blue"]}
#
# the_supplier = Supplier.find(:first)
# the_supplier.name # => 'J&M Textiles'
@@ -906,7 +907,7 @@ module ActiveResource
case value
when Array
resource = find_or_create_resource_for_collection(key)
- value.map { |attrs| resource.new(attrs) }
+ value.map { |attrs| attrs.is_a?(String) ? attrs.dup : resource.new(attrs) }
when Hash
resource = find_or_create_resource_for(key)
resource.new(value)
diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb
index 7460fd45b0..bb08098683 100644
--- a/activeresource/test/base_test.rb
+++ b/activeresource/test/base_test.rb
@@ -46,10 +46,24 @@ class BaseTest < Test::Unit::TestCase
:children => [{:name => 'Natacha'}]},
{:name => 'Milena',
:children => []}]}]}.to_xml(:root => 'customer')
+ # - resource with yaml array of strings; for ActiveRecords using serialize :bar, Array
+ @marty = <<-eof
+ <?xml version=\"1.0\" encoding=\"UTF-8\"?>
+ <person>
+ <id type=\"integer\">5</id>
+ <name>Marty</name>
+ <colors type=\"yaml\">---
+ - \"red\"
+ - \"green\"
+ - \"blue\"
+ </colors>
+ </person>
+ eof
ActiveResource::HttpMock.respond_to do |mock|
mock.get "/people/1.xml", {}, @matz
mock.get "/people/2.xml", {}, @david
+ mock.get "/people/5.xml", {}, @marty
mock.get "/people/Greg.xml", {}, @greg
mock.get "/people/4.xml", {'key' => 'value'}, nil, 404
mock.put "/people/1.xml", {}, nil, 204
@@ -851,4 +865,14 @@ class BaseTest < Test::Unit::TestCase
end
end
end
+
+ def test_load_yaml_array
+ assert_nothing_raised do
+ marty = Person.find(5)
+ assert_equal 3, marty.colors.size
+ marty.colors.each do |color|
+ assert_kind_of String, color
+ end
+ end
+ end
end
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 9700a11531..2c6f4ed582 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* Add ActiveSupport::Rescuable module abstracting ActionController::Base rescue_from features. [Norbert Crombach, Pratik]
+
* Switch from String#chars to String#mb_chars for the unicode proxy. [Manfred Stienstra]
This helps with 1.8.7 compatibility and also improves performance for some operations by reducing indirection.
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index b30faff06d..0ff09067ec 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -56,6 +56,8 @@ require 'active_support/time_with_zone'
require 'active_support/secure_random'
+require 'active_support/rescuable'
+
I18n.load_path << File.dirname(__FILE__) + '/active_support/locale/en-US.yml'
Inflector = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Inflector', 'ActiveSupport::Inflector')
diff --git a/activesupport/lib/active_support/core_ext/object/extending.rb b/activesupport/lib/active_support/core_ext/object/extending.rb
index 082e98a297..bbf6f8563b 100644
--- a/activesupport/lib/active_support/core_ext/object/extending.rb
+++ b/activesupport/lib/active_support/core_ext/object/extending.rb
@@ -3,22 +3,43 @@ class Object
Class.remove_class(*subclasses_of(*superclasses))
end
- # Exclude this class unless it's a subclass of our supers and is defined.
- # We check defined? in case we find a removed class that has yet to be
- # garbage collected. This also fails for anonymous classes -- please
- # submit a patch if you have a workaround.
- def subclasses_of(*superclasses) #:nodoc:
- subclasses = []
-
- superclasses.each do |sup|
- ObjectSpace.each_object(class << sup; self; end) do |k|
- if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
- subclasses << k
+ begin
+ ObjectSpace.each_object(Class.new) {}
+
+ # Exclude this class unless it's a subclass of our supers and is defined.
+ # We check defined? in case we find a removed class that has yet to be
+ # garbage collected. This also fails for anonymous classes -- please
+ # submit a patch if you have a workaround.
+ def subclasses_of(*superclasses) #:nodoc:
+ subclasses = []
+
+ superclasses.each do |sup|
+ ObjectSpace.each_object(class << sup; self; end) do |k|
+ if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
+ subclasses << k
+ end
end
end
+
+ subclasses
end
+ rescue RuntimeError
+ # JRuby and any implementations which cannot handle the objectspace traversal
+ # above fall back to this implementation
+ def subclasses_of(*superclasses) #:nodoc:
+ subclasses = []
- subclasses
+ superclasses.each do |sup|
+ ObjectSpace.each_object(Class) do |k|
+ if superclasses.any? { |superclass| k < superclass } &&
+ (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
+ subclasses << k
+ end
+ end
+ subclasses.uniq!
+ end
+ subclasses
+ end
end
def extended_by #:nodoc:
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
new file mode 100644
index 0000000000..f2bc12e832
--- /dev/null
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -0,0 +1,108 @@
+module ActiveSupport
+ # Rescuable module adds support for easier exception handling.
+ module Rescuable
+ def self.included(base) # :nodoc:
+ base.class_inheritable_accessor :rescue_handlers
+ base.rescue_handlers = []
+
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ # Rescue exceptions raised in controller actions.
+ #
+ # <tt>rescue_from</tt> receives a series of exception classes or class
+ # names, and a trailing <tt>:with</tt> option with the name of a method
+ # or a Proc object to be called to handle them. Alternatively a block can
+ # be given.
+ #
+ # Handlers that take one argument will be called with the exception, so
+ # that the exception can be inspected when dealing with it.
+ #
+ # Handlers are inherited. They are searched from right to left, from
+ # bottom to top, and up the hierarchy. The handler of the first class for
+ # which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
+ # any.
+ #
+ # class ApplicationController < ActionController::Base
+ # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
+ # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
+ #
+ # rescue_from 'MyAppError::Base' do |exception|
+ # render :xml => exception, :status => 500
+ # end
+ #
+ # protected
+ # def deny_access
+ # ...
+ # end
+ #
+ # def show_errors(exception)
+ # exception.record.new_record? ? ...
+ # end
+ # end
+ def rescue_from(*klasses, &block)
+ options = klasses.extract_options!
+
+ unless options.has_key?(:with)
+ if block_given?
+ options[:with] = block
+ else
+ raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument."
+ end
+ end
+
+ klasses.each do |klass|
+ key = if klass.is_a?(Class) && klass <= Exception
+ klass.name
+ elsif klass.is_a?(String)
+ klass
+ else
+ raise ArgumentError, "#{klass} is neither an Exception nor a String"
+ end
+
+ # put the new handler at the end because the list is read in reverse
+ rescue_handlers << [key, options[:with]]
+ end
+ end
+ end
+
+ # Tries to rescue the exception by looking up and calling a registered handler.
+ def rescue_with_handler(exception)
+ if handler = handler_for_rescue(exception)
+ handler.arity != 0 ? handler.call(exception) : handler.call
+ true # don't rely on the return value of the handler
+ end
+ end
+
+ def handler_for_rescue(exception)
+ # We go from right to left because pairs are pushed onto rescue_handlers
+ # as rescue_from declarations are found.
+ _, handler = Array(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.
+ #
+ # Since this loop needs the constants it would be inconsistent to
+ # assume they should exist at this point. An early raised exception
+ # could trigger some other handler and the array could include
+ # precisely a string whose corresponding constant has not yet been
+ # seen. This is why we are tolerant to unknown constants.
+ #
+ # Note that this tolerance only matters if the exception was given as
+ # a string, otherwise a NameError will be raised by the interpreter
+ # itself when rescue_from CONSTANT is executed.
+ klass = self.class.const_get(klass_name) rescue nil
+ klass ||= klass_name.constantize rescue nil
+ exception.is_a?(klass) if klass
+ end
+
+ case handler
+ when Symbol
+ method(handler)
+ when Proc
+ handler.bind(self)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/secure_random.rb b/activesupport/lib/active_support/secure_random.rb
index 688165f9a3..97971e8830 100644
--- a/activesupport/lib/active_support/secure_random.rb
+++ b/activesupport/lib/active_support/secure_random.rb
@@ -164,13 +164,13 @@ module ActiveSupport
hex = n.to_s(16)
hex = '0' + hex if (hex.length & 1) == 1
bin = [hex].pack("H*")
- mask = bin[0].ord
+ mask = bin[0]
mask |= mask >> 1
mask |= mask >> 2
mask |= mask >> 4
begin
rnd = SecureRandom.random_bytes(bin.length)
- rnd[0] = (rnd[0].ord & mask).chr
+ rnd[0] = rnd[0] & mask
end until rnd < bin
rnd.unpack("H*")[0].hex
else
diff --git a/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb
index 63d1ba6507..e5853bf828 100644
--- a/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb
+++ b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb
@@ -37,15 +37,18 @@ module Test
# end
def assert_difference(expressions, difference = 1, message = nil, &block)
expression_evaluations = Array(expressions).map do |expression|
- lambda do
+ [expression, lambda do
eval(expression, block.__send__(:binding))
- end
+ end]
end
- original_values = expression_evaluations.inject([]) { |memo, expression| memo << expression.call }
+ original_values = expression_evaluations.inject([]) { |memo, expression| memo << expression[1].call }
yield
expression_evaluations.each_with_index do |expression, i|
- assert_equal original_values[i] + difference, expression.call, message
+ full_message = ""
+ full_message << "#{message}.\n" if message
+ full_message << "<#{expression[0]}> was the expression that failed"
+ assert_equal original_values[i] + difference, expression[1].call, full_message
end
end
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
new file mode 100644
index 0000000000..9f2b783b2e
--- /dev/null
+++ b/activesupport/test/rescuable_test.rb
@@ -0,0 +1,75 @@
+require 'abstract_unit'
+
+class WraithAttack < StandardError
+end
+
+class NuclearExplosion < StandardError
+end
+
+class MadRonon < StandardError
+ attr_accessor :message
+
+ def initialize(message)
+ @message = message
+ super()
+ end
+end
+
+class Stargate
+ attr_accessor :result
+
+ include ActiveSupport::Rescuable
+
+ rescue_from WraithAttack, :with => :sos
+
+ rescue_from NuclearExplosion do
+ @result = 'alldead'
+ end
+
+ rescue_from MadRonon do |e|
+ @result = e.message
+ end
+
+ def dispatch(method)
+ send(method)
+ rescue Exception => e
+ rescue_with_handler(e)
+ end
+
+ def attack
+ raise WraithAttack
+ end
+
+ def nuke
+ raise NuclearExplosion
+ end
+
+ def ronanize
+ raise MadRonon.new("dex")
+ end
+
+ def sos
+ @result = 'killed'
+ end
+end
+
+class RescueableTest < Test::Unit::TestCase
+ def setup
+ @stargate = Stargate.new
+ end
+
+ def test_rescue_from_with_method
+ @stargate.dispatch :attack
+ assert_equal 'killed', @stargate.result
+ end
+
+ def test_rescue_from_with_block
+ @stargate.dispatch :nuke
+ assert_equal 'alldead', @stargate.result
+ end
+
+ def test_rescue_from_with_block_with_args
+ @stargate.dispatch :ronanize
+ assert_equal 'dex', @stargate.result
+ end
+end
diff --git a/activesupport/test/secure_random_test.rb b/activesupport/test/secure_random_test.rb
index b0b6c21a81..44694cd811 100644
--- a/activesupport/test/secure_random_test.rb
+++ b/activesupport/test/secure_random_test.rb
@@ -12,4 +12,8 @@ class SecureRandomTest < Test::Unit::TestCase
b2 = ActiveSupport::SecureRandom.hex(64)
assert_not_equal b1, b2
end
+
+ def test_random_number
+ assert ActiveSupport::SecureRandom.random_number(5000) < 5000
+ end
end
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index 26a45af255..4e253848f6 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -60,6 +60,24 @@ class AssertDifferenceTest < Test::Unit::TestCase
@object.increment
end
end
+
+ def test_array_of_expressions_identify_failure
+ assert_difference ['@object.num', '1 + 1'] do
+ @object.increment
+ end
+ fail 'should not get to here'
+ rescue Test::Unit::AssertionFailedError => e
+ assert_equal "<1 + 1> was the expression that failed.\n<3> expected but was\n<2>.", e.message
+ end
+
+ def test_array_of_expressions_identify_failure_when_message_provided
+ assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do
+ @object.increment
+ end
+ fail 'should not get to here'
+ rescue Test::Unit::AssertionFailedError => e
+ assert_equal "something went wrong.\n<1 + 1> was the expression that failed.\n<3> expected but was\n<2>.", e.message
+ end
else
def default_test; end
end
diff --git a/railties/configs/databases/ibm_db.yml b/railties/configs/databases/ibm_db.yml
new file mode 100644
index 0000000000..a9716ddb44
--- /dev/null
+++ b/railties/configs/databases/ibm_db.yml
@@ -0,0 +1,62 @@
+# IBM Dataservers
+#
+# Home Page
+# http://rubyforge.org/projects/rubyibm/
+#
+# To install the ibm_db gem:
+# On Linux:
+# Source the db2profile file and set the necessary environment variables:
+#
+# . /home/db2inst1/sqllib/db2profile
+# export IBM_DB_DIR=/opt/ibm/db2/V9.1
+# export IBM_DB_LIB=/opt/ibm/db2/V9.1/lib32
+#
+# Then issue the command: gem install ibm_db
+#
+# On Windows:
+# Issue the command: gem install ibm_db
+# If prompted, select the mswin32 option
+#
+# For more details on the installation refer to http://rubyforge.org/docman/view.php/2361/7682/IBM_DB_GEM.pdf
+#
+# For more details on the connection parameters below refer to:
+# http://rubyibm.rubyforge.org/docs/adapter/0.9.0/rdoc/classes/ActiveRecord/ConnectionAdapters/IBM_DBAdapter.html
+
+development:
+ adapter: ibm_db
+ username: db2inst1
+ password:
+ database: <%= app_name[0,4] %>_dev
+ #schema: db2inst1
+ #host: localhost
+ #port: 50000
+ #account: my_account
+ #app_user: my_app_user
+ #application: my_application
+ #workstation: my_workstation
+
+test:
+ adapter: ibm_db
+ username: db2inst1
+ password:
+ database: <%= app_name[0,4] %>_tst
+ #schema: db2inst1
+ #host: localhost
+ #port: 50000
+ #account: my_account
+ #app_user: my_app_user
+ #application: my_application
+ #workstation: my_workstation
+
+production:
+ adapter: ibm_db
+ username: db2inst1
+ password:
+ database: <%= app_name[0,4] %>_prd
+ #schema: db2inst1
+ #host: localhost
+ #port: 50000
+ #account: my_account
+ #app_user: my_app_user
+ #application: my_application
+ #workstation: my_workstation \ No newline at end of file
diff --git a/railties/doc/guides/actionview/layouts_and_rendering.txt b/railties/doc/guides/actionview/layouts_and_rendering.txt
index 5a370500e5..278cca20a6 100644
--- a/railties/doc/guides/actionview/layouts_and_rendering.txt
+++ b/railties/doc/guides/actionview/layouts_and_rendering.txt
@@ -650,6 +650,15 @@ _form.html.erb:
Although the same partial will be rendered into both views, the label on the submit button is controlled by a local variable passed into the partial.
+Every partial also has a local variable with the same name as the partial (minus the underscore). By default, it will look for an instance variable with the same name as the partial in the parent. You can pass an object in to this local variable via the +:object+ option:
+
+[source, html]
+-------------------------------------------------------
+<%= render :partial => "customer", :object => @new_customer %>
+-------------------------------------------------------
+
+Within the +customer+ partial, the +@customer+ variable will refer to +@new_customer+ from the parent view.
+
==== Rendering Collections
Partials are very useful in rendering collections. When you pass a collection to a partial via the +:collection+ option, the partial will be inserted once for each member in the collection:
@@ -666,12 +675,29 @@ _product.html.erb:
<p>Product Name: <%= product.name %></p>
-------------------------------------------------------
-When a partial is called with a pluralized collection, then the individual instances of the partial have access to the member of the collection being rendered via a singularized variable. In this case, the collection is +@products+, and within the +_product+ partial, you can refer to +product+ to get the instance that is being rendered.
+When a partial is called with a pluralized collection, then the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is +_product, and within the +_product+ partial, you can refer to +product+ to get the instance that is being rendered. To use a custom local variable name within the partial, specify the +:as+ option in the call to the partial:
+
+[source, html]
+-------------------------------------------------------
+<%= render :partial => "product", :collection => @products, :as => :item %>
+-------------------------------------------------------
+
+With this change, you can access an instance of the +@products+ collection as the +item+ local variable within the partial.
+
+You can also specify a second partial to be rendered between instances of the main partial by using the +:spacer_template+ option:
+
+[source, html]
+-------------------------------------------------------
+<%= render :partial => "product", :collection => @products, :spacer_template => "product_ruler" %>
+-------------------------------------------------------
+
+Rails will render the +_product_ruler+ partial (with no data passed in to it) between each pair of +_product+ partials.
== Changelog ==
http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/15[Lighthouse ticket]
+* October 4, 2008: Additional info on partials (+:object+, +:as+, and +:spacer_template+) by link:../authors.html#mgunderloy[Mike Gunderloy] (not yet approved for publication)
* September 28, 2008: First draft by link:../authors.html#mgunderloy[Mike Gunderloy] (not yet approved for publication)
diff --git a/railties/doc/guides/activerecord/association_basics.txt b/railties/doc/guides/activerecord/association_basics.txt
index b91052f91f..f9ec7a5f55 100644
--- a/railties/doc/guides/activerecord/association_basics.txt
+++ b/railties/doc/guides/activerecord/association_basics.txt
@@ -698,8 +698,6 @@ end
Counter cache columns are added to the containing model's list of read-only attributes through +attr_readonly+.
-WARNING: When you create a counter cache column in the database, be sure to specify a default value of zero. Otherwise, Rails will not properly maintain the counter.
-
===== +:dependent+
If you set the +:dependent+ option to +:destroy+, then deleting this object will call the destroy method on the associated object to delete that object. If you set the +:dependent+ option to +:delete+, then deleting this object will delete the associated object _without_ calling its +destroy+ method.
diff --git a/railties/doc/guides/routing/routing_outside_in.txt b/railties/doc/guides/routing/routing_outside_in.txt
index 2c1870d9d4..f35ac0cebd 100644
--- a/railties/doc/guides/routing/routing_outside_in.txt
+++ b/railties/doc/guides/routing/routing_outside_in.txt
@@ -568,7 +568,12 @@ map.resources :photos, :member => { :preview => :get }
This will enable Rails to recognize URLs such as +/photos/1/preview+ using the GET HTTP verb, and route them to the preview action of the Photos controller. It will also create a +preview_photo+ route helper.
-Within the hash of member routes, each route name specifies the HTTP verb that it will recognize. You can use +:get+, +:put+, +:post+, +:delete+, or +:any+ here.
+Within the hash of member routes, each route name specifies the HTTP verb that it will recognize. You can use +:get+, +:put+, +:post+, +:delete+, or +:any+ here. You can also specify an array of methods, if you need more than one but you don't want to allow just anything:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :member => { :prepare => [:get, :post] }
+-------------------------------------------------------
==== Adding Collection Routes
@@ -581,6 +586,13 @@ map.resources :photos, :collection => { :search => :get }
This will enable Rails to recognize URLs such as +/photos/search+ using the GET HTTP verb, and route them to the search action of the Photos controller. It will also create a +search_photos+ route helper.
+Just as with member routes, you can specify an array of methods for a collection route:
+
+[source, ruby]
+-------------------------------------------------------
+map.resources :photos, :collection => { :search => [:get, :post] }
+-------------------------------------------------------
+
==== Adding New Routes
To add a new route (one that creates a new resource), use the +:new+ option:
@@ -904,5 +916,6 @@ assert_routing { :path => "photos", :method => :post }, { :controller => "photos
http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/3[Lighthouse ticket]
-* September 10, 2008: initial version by link:../authors.html#mgunderloy[Mike Gunderloy]
+* October 4, 2008: Added additional detail on specifying verbs for resource member/collection routes , by link:../authors.html#mgunderloy[Mike Gunderloy]
* September 23, 2008: Added section on namespaced controllers and routing, by link:../authors.html#mgunderloy[Mike Gunderloy]
+* September 10, 2008: initial version by link:../authors.html#mgunderloy[Mike Gunderloy]
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index 74d2daa34b..0aec97dece 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -212,6 +212,7 @@ module Rails
Gem.loaded_specs[stub] = Gem::Specification.new do |s|
s.name = stub
s.version = Rails::VERSION::STRING
+ s.loaded_from = ""
end
end
end
@@ -878,7 +879,6 @@ Run `rake gems:install` to install the missing gems.
components
config
lib
- vendor
).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
paths.concat builtin_directories
diff --git a/railties/lib/rails/gem_builder.rb b/railties/lib/rails/gem_builder.rb
index e7e06d0008..79c61cc034 100644
--- a/railties/lib/rails/gem_builder.rb
+++ b/railties/lib/rails/gem_builder.rb
@@ -12,10 +12,10 @@ module Rails
@spec = spec
@gem_dir = gem_dir
end
-
+
# silence the underlying builder
def say(message)
end
-
+
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb
index d58ae450eb..3b2f4846a6 100644
--- a/railties/lib/rails/gem_dependency.rb
+++ b/railties/lib/rails/gem_dependency.rb
@@ -1,50 +1,76 @@
+require 'rails/vendor_gem_source_index'
+
+module Gem
+ def self.source_index=(index)
+ @@source_index = index
+ end
+end
+
module Rails
class GemDependency
- attr_accessor :name, :requirement, :version, :lib, :source
+ attr_accessor :lib, :source
def self.unpacked_path
@unpacked_path ||= File.join(RAILS_ROOT, 'vendor', 'gems')
end
+ @@framework_gems = {}
+
+ def self.add_frozen_gem_path
+ @@paths_loaded ||= begin
+ Gem.source_index = Rails::VendorGemSourceIndex.new(Gem.source_index)
+ # loaded before us - we can't change them, so mark them
+ Gem.loaded_specs.each do |name, spec|
+ @@framework_gems[name] = spec
+ end
+ end
+ end
+
+ def framework_gem?
+ @@framework_gems.has_key?(name)
+ end
+
+ def vendor_rails?
+ Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty?
+ end
+
+ def vendor_gem?
+ Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.include?(self.class.unpacked_path)
+ end
+
def initialize(name, options = {})
require 'rubygems' unless Object.const_defined?(:Gem)
if options[:requirement]
- @requirement = options[:requirement]
+ req = options[:requirement]
elsif options[:version]
- @requirement = Gem::Requirement.create(options[:version])
+ req = Gem::Requirement.create(options[:version])
+ else
+ req = Gem::Requirement.default
end
- @version = @requirement.instance_variable_get("@requirements").first.last if @requirement
- @name = name.to_s
+ @dep = Gem::Dependency.new(name, req)
@lib = options[:lib]
@source = options[:source]
@loaded = @frozen = @load_paths_added = false
- @unpack_directory = nil
- end
-
- def unpacked_paths
- Dir[File.join(self.class.unpacked_path, "#{@name}-#{@version || "*"}")]
end
def add_load_paths
+ self.class.add_frozen_gem_path
return if @loaded || @load_paths_added
- unpacked_paths = self.unpacked_paths
- if unpacked_paths.empty?
- args = [@name]
- args << @requirement.to_s if @requirement
- gem *args
- else
- $LOAD_PATH.unshift File.join(unpacked_paths.first, 'lib')
- ext = File.join(unpacked_paths.first, 'ext')
- $LOAD_PATH.unshift(ext) if File.exist?(ext)
- @frozen = true
+ if framework_gem?
+ @load_paths_added = @loaded = @frozen = true
+ return
end
+ gem @dep
+ @spec = Gem.loaded_specs[name]
+ @frozen = @spec.loaded_from.include?(self.class.unpacked_path) if @spec
@load_paths_added = true
rescue Gem::LoadError
end
def dependencies
+ return [] if framework_gem?
all_dependencies = specification.dependencies.map do |dependency|
GemDependency.new(dependency.name, :requirement => dependency.version_requirements)
end
@@ -58,22 +84,51 @@ module Rails
def load
return if @loaded || @load_paths_added == false
- require(@lib || @name) unless @lib == false
+ require(@lib || name) unless @lib == false
@loaded = true
rescue LoadError
puts $!.to_s
$!.backtrace.each { |b| puts b }
end
+ def name
+ @dep.name.to_s
+ end
+
+ def requirement
+ r = @dep.version_requirements
+ (r == Gem::Requirement.default) ? nil : r
+ end
+
def frozen?
- @frozen
+ @frozen ||= vendor_rails? || vendor_gem?
end
def loaded?
- @loaded
+ @loaded ||= begin
+ if vendor_rails?
+ true
+ else
+ # check if the gem is loaded by inspecting $"
+ # specification.files lists all the files contained in the gem
+ gem_files = specification.files
+ # select only the files contained in require_paths - typically in bin and lib
+ require_paths_regexp = Regexp.new("^(#{specification.require_paths*'|'})/")
+ gem_lib_files = gem_files.select { |f| require_paths_regexp.match(f) }
+ # chop the leading directory off - a typical file might be in
+ # lib/gem_name/file_name.rb, but it will be 'require'd as gem_name/file_name.rb
+ gem_lib_files.map! { |f| f.split('/', 2)[1] }
+ # if any of the files from the above list appear in $", the gem is assumed to
+ # have been loaded
+ !(gem_lib_files & $").empty?
+ end
+ end
end
def load_paths_added?
+ # always try to add load paths - even if a gem is loaded, it may not
+ # be a compatible version (ie random_gem 0.4 is loaded and a later spec
+ # needs >= 0.5 - gem 'random_gem' will catch this and error out)
@load_paths_added
end
@@ -93,17 +148,44 @@ module Rails
# we can access information about the gem on deployment systems
# without having the gem installed
spec_filename = File.join(gem_dir(directory), '.specification')
+ # Gem.activate changes the spec - get the original
+ spec = Gem::Specification.load(specification.loaded_from)
File.open(spec_filename, 'w') do |file|
- file.puts specification.to_yaml
+ file.puts spec.to_yaml
end
end
def ==(other)
self.name == other.name && self.requirement == other.requirement
end
+ alias_method :"eql?", :"=="
+
+ def hash
+ @dep.hash
+ end
def specification
- @spec ||= Gem.source_index.search(Gem::Dependency.new(@name, @requirement)).sort_by { |s| s.version }.last
+ # code repeated from Gem.activate. Find a matching spec, or the currently loaded version.
+ # error out if loaded version and requested version are incompatible.
+ @spec ||= begin
+ matches = Gem.source_index.search(@dep)
+ matches << @@framework_gems[name] if framework_gem?
+ if Gem.loaded_specs[name] then
+ # This gem is already loaded. If the currently loaded gem is not in the
+ # list of candidate gems, then we have a version conflict.
+ existing_spec = Gem.loaded_specs[name]
+ unless matches.any? { |spec| spec.version == existing_spec.version } then
+ raise Gem::Exception,
+ "can't activate #{@dep}, already activated #{existing_spec.full_name}"
+ end
+ # we're stuck with it, so change to match
+ @dep.version_requirements = Gem::Requirement.create("=#{existing_spec.version}")
+ existing_spec
+ else
+ # new load
+ matches.last
+ end
+ end
end
private
@@ -112,17 +194,15 @@ module Rails
end
def install_command
- cmd = %w(install) << @name
- cmd << "--version" << %("#{@requirement.to_s}") if @requirement
+ cmd = %w(install) << name
+ cmd << "--version" << %("#{requirement.to_s}") if requirement
cmd << "--source" << @source if @source
cmd
end
def unpack_command
- cmd = %w(unpack) << @name
- # We don't quote this requirement as it's run through GemRunner instead
- # of shelling out to gem
- cmd << "--version" << @requirement.to_s if @requirement
+ cmd = %w(unpack) << name
+ cmd << "--version" << "= "+specification.version.to_s if requirement
cmd
end
end
diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
index b8b2b57038..4d983843af 100644
--- a/railties/lib/rails/plugin.rb
+++ b/railties/lib/rails/plugin.rb
@@ -112,7 +112,7 @@ module Rails
class GemPlugin < Plugin
# Initialize this plugin from a Gem::Specification.
def initialize(spec, gem)
- directory = (gem.frozen? && gem.unpacked_paths.first) || File.join(spec.full_gem_path)
+ directory = spec.full_gem_path
super(directory)
@name = spec.name
end
diff --git a/railties/lib/rails/vendor_gem_source_index.rb b/railties/lib/rails/vendor_gem_source_index.rb
new file mode 100644
index 0000000000..c8701101e2
--- /dev/null
+++ b/railties/lib/rails/vendor_gem_source_index.rb
@@ -0,0 +1,93 @@
+require 'rubygems'
+require 'yaml'
+
+module Rails
+
+ class VendorGemSourceIndex
+ # VendorGemSourceIndex acts as a proxy for the Gem source index, allowing
+ # gems to be loaded from vendor/gems. Rather than the standard gem repository format,
+ # vendor/gems contains unpacked gems, with YAML specifications in .specification in
+ # each gem directory.
+ include Enumerable
+
+ attr_reader :installed_source_index
+ attr_reader :vendor_source_index
+
+ def initialize(installed_index, vendor_dir=Rails::GemDependency.unpacked_path)
+ @installed_source_index = installed_index
+ @vendor_dir = vendor_dir
+ refresh!
+ end
+
+ def refresh!
+ # reload the installed gems
+ @installed_source_index.refresh!
+ vendor_gems = {}
+
+ # handle vendor Rails gems - they are identified by having loaded_from set to ""
+ # we add them manually to the list, so that other gems can find them via dependencies
+ Gem.loaded_specs.each do |n, s|
+ next unless s.loaded_from.empty?
+ vendor_gems[s.full_name] = s
+ end
+
+ # load specifications from vendor/gems
+ Dir[File.join(Rails::GemDependency.unpacked_path, '*')].each do |d|
+ spec = load_specification(d)
+ next unless spec
+ # NOTE: this is a bit of a hack - the gem system expects a different structure
+ # than we have.
+ # It's looking for:
+ # repository
+ # -> specifications
+ # - gem_name.spec <= loaded_from points to this
+ # -> gems
+ # - gem_name <= gem files here
+ # and therefore goes up one directory from loaded_from, then adds gems/gem_name
+ # to the path.
+ # But we have:
+ # vendor
+ # -> gems
+ # -> gem_name <= gem files here
+ # - .specification
+ # so we set loaded_from to vendor/gems/.specification (not a real file) to
+ # get the correct behavior.
+ spec.loaded_from = File.join(Rails::GemDependency.unpacked_path, '.specification')
+ vendor_gems[File.basename(d)] = spec
+ end
+ @vendor_source_index = Gem::SourceIndex.new(vendor_gems)
+ end
+
+ def load_specification(gem_dir)
+ spec_file = File.join(gem_dir, '.specification')
+ YAML.load_file(spec_file) if File.exist?(spec_file)
+ end
+
+ def find_name(gem_name, version_requirement = Gem::Requirement.default)
+ search(/^#{gem_name}$/, version_requirement)
+ end
+
+ def search(*args)
+ # look for vendor gems, and then installed gems - later elements take priority
+ @installed_source_index.search(*args) + @vendor_source_index.search(*args)
+ end
+
+ def each(&block)
+ @vendor_source_index.each(&block)
+ @installed_source_index.each(&block)
+ end
+
+ def add_spec(spec)
+ @vendor_source_index.add_spec spec
+ end
+
+ def remove_spec(spec)
+ @vendor_source_index.remove_spec spec
+ end
+
+ def size
+ @vendor_source_index.size + @installed_source_index.size
+ end
+
+ end
+end \ No newline at end of file
diff --git a/railties/lib/rails_generator/generators/applications/app/app_generator.rb b/railties/lib/rails_generator/generators/applications/app/app_generator.rb
index 9849948339..acd3dc8c8c 100644
--- a/railties/lib/rails_generator/generators/applications/app/app_generator.rb
+++ b/railties/lib/rails_generator/generators/applications/app/app_generator.rb
@@ -5,7 +5,7 @@ class AppGenerator < Rails::Generator::Base
DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'],
Config::CONFIG['ruby_install_name'])
- DATABASES = %w(mysql oracle postgresql sqlite2 sqlite3 frontbase)
+ DATABASES = %w(mysql oracle postgresql sqlite2 sqlite3 frontbase ibm_db)
DEFAULT_DATABASE = 'sqlite3'
default_options :db => (ENV["RAILS_DEFAULT_DATABASE"] || DEFAULT_DATABASE),
diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake
index 1431aa6944..5cb27f1f10 100644
--- a/railties/lib/tasks/databases.rake
+++ b/railties/lib/tasks/databases.rake
@@ -341,7 +341,7 @@ namespace :db do
case abcs["test"]["adapter"]
when "mysql"
ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"])
+ ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"])
when "postgresql"
ActiveRecord::Base.clear_active_connections!
drop_database(abcs['test'])
diff --git a/railties/lib/tasks/gems.rake b/railties/lib/tasks/gems.rake
index 0321e60e0f..9abdfc56b6 100644
--- a/railties/lib/tasks/gems.rake
+++ b/railties/lib/tasks/gems.rake
@@ -1,14 +1,19 @@
desc "List the gems that this rails application depends on"
task :gems => 'gems:base' do
Rails.configuration.gems.each do |gem|
- code = gem.loaded? ? (gem.frozen? ? "F" : "I") : " "
- puts "[#{code}] #{gem.name} #{gem.requirement.to_s}"
+ print_gem_status(gem)
end
puts
puts "I = Installed"
puts "F = Frozen"
end
+def print_gem_status(gem, indent=1)
+ code = gem.loaded? ? (gem.frozen? ? "F" : "I") : " "
+ puts " "*(indent-1)+" - [#{code}] #{gem.name} #{gem.requirement.to_s}"
+ gem.dependencies.each { |g| print_gem_status(g, indent+1)}
+end
+
namespace :gems do
task :base do
$rails_gem_installer = true
@@ -19,7 +24,7 @@ namespace :gems do
task :build do
$rails_gem_installer = true
require 'rails/gem_builder'
- Dir[File.join(RAILS_ROOT, 'vendor', 'gems', '*')].each do |gem_dir|
+ Dir[File.join(Rails::GemDependency.unpacked_path, '*')].each do |gem_dir|
spec_file = File.join(gem_dir, '.specification')
next unless File.exists?(spec_file)
specification = YAML::load_file(spec_file)
@@ -28,7 +33,7 @@ namespace :gems do
puts "Built gem: '#{gem_dir}'"
end
end
-
+
desc "Installs all required gems for this application."
task :install => :base do
require 'rubygems'
@@ -42,10 +47,10 @@ namespace :gems do
require 'rubygems/gem_runner'
Rails.configuration.gems.each do |gem|
next unless !gem.frozen? && (ENV['GEM'].blank? || ENV['GEM'] == gem.name)
- gem.unpack_to(File.join(RAILS_ROOT, 'vendor', 'gems')) if gem.loaded?
+ gem.unpack_to(Rails::GemDependency.unpacked_path) if gem.loaded?
end
end
-
+
namespace :unpack do
desc "Unpacks the specified gems and its dependencies into vendor/gems"
task :dependencies => :unpack do
@@ -54,9 +59,8 @@ namespace :gems do
Rails.configuration.gems.each do |gem|
next unless ENV['GEM'].blank? || ENV['GEM'] == gem.name
gem.dependencies.each do |dependency|
- dependency.add_load_paths # double check that we have not already unpacked
next if dependency.frozen?
- dependency.unpack_to(File.join(RAILS_ROOT, 'vendor', 'gems'))
+ dependency.unpack_to(Rails::GemDependency.unpacked_path)
end
end
end
diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb
index c94c950455..89e25341d1 100644
--- a/railties/test/gem_dependency_test.rb
+++ b/railties/test/gem_dependency_test.rb
@@ -37,39 +37,78 @@ uses_mocha "Plugin Tests" do
end
def test_gem_with_version_unpack_install_command
+ # stub out specification method, or else test will fail if hpricot 0.6 isn't installed
+ mock_spec = mock()
+ mock_spec.stubs(:version).returns('0.6')
+ @gem_with_version.stubs(:specification).returns(mock_spec)
assert_equal ["unpack", "hpricot", "--version", '= 0.6'], @gem_with_version.unpack_command
end
def test_gem_adds_load_paths
- @gem.expects(:gem).with(@gem.name)
+ @gem.expects(:gem).with(Gem::Dependency.new(@gem.name, nil))
@gem.add_load_paths
end
def test_gem_with_version_adds_load_paths
- @gem_with_version.expects(:gem).with(@gem_with_version.name, @gem_with_version.requirement.to_s)
+ @gem_with_version.expects(:gem).with(Gem::Dependency.new(@gem_with_version.name, @gem_with_version.requirement.to_s))
@gem_with_version.add_load_paths
end
def test_gem_loading
- @gem.expects(:gem).with(@gem.name)
+ @gem.expects(:gem).with(Gem::Dependency.new(@gem.name, nil))
@gem.expects(:require).with(@gem.name)
@gem.add_load_paths
@gem.load
end
def test_gem_with_lib_loading
- @gem_with_lib.expects(:gem).with(@gem_with_lib.name)
+ @gem_with_lib.expects(:gem).with(Gem::Dependency.new(@gem_with_lib.name, nil))
@gem_with_lib.expects(:require).with(@gem_with_lib.lib)
@gem_with_lib.add_load_paths
@gem_with_lib.load
end
def test_gem_without_lib_loading
- @gem_without_load.expects(:gem).with(@gem_without_load.name)
+ @gem_without_load.expects(:gem).with(Gem::Dependency.new(@gem_without_load.name, nil))
@gem_without_load.expects(:require).with(@gem_without_load.lib).never
@gem_without_load.add_load_paths
@gem_without_load.load
end
+ def test_gem_dependencies_compare_for_uniq
+ gem1 = Rails::GemDependency.new "gem1"
+ gem1a = Rails::GemDependency.new "gem1"
+ gem2 = Rails::GemDependency.new "gem2"
+ gem2a = Rails::GemDependency.new "gem2"
+ gem3 = Rails::GemDependency.new "gem2", :version => ">=0.1"
+ gem3a = Rails::GemDependency.new "gem2", :version => ">=0.1"
+ gem4 = Rails::GemDependency.new "gem3"
+ gems = [gem1, gem2, gem1a, gem3, gem2a, gem4, gem3a, gem2, gem4]
+ assert_equal 4, gems.uniq.size
+ end
+
+ def test_gem_load_frozen
+ dummy_gem = Rails::GemDependency.new "dummy-gem-a"
+ dummy_gem.add_load_paths
+ dummy_gem.load
+ assert_not_nil DUMMY_GEM_A_VERSION
+ end
+
+ def test_gem_load_frozen_specific_version
+ dummy_gem = Rails::GemDependency.new "dummy-gem-b", :version => '0.4.0'
+ dummy_gem.add_load_paths
+ dummy_gem.load
+ assert_not_nil DUMMY_GEM_B_VERSION
+ assert_equal '0.4.0', DUMMY_GEM_B_VERSION
+ end
+
+ def test_gem_load_frozen_minimum_version
+ dummy_gem = Rails::GemDependency.new "dummy-gem-c", :version => '>=0.5.0'
+ dummy_gem.add_load_paths
+ dummy_gem.load
+ assert_not_nil DUMMY_GEM_C_VERSION
+ assert_equal '0.6.0', DUMMY_GEM_C_VERSION
+ end
+
end
end
diff --git a/railties/test/vendor/gems/dummy-gem-a-0.4.0/.specification b/railties/test/vendor/gems/dummy-gem-a-0.4.0/.specification
new file mode 100644
index 0000000000..86dba2092c
--- /dev/null
+++ b/railties/test/vendor/gems/dummy-gem-a-0.4.0/.specification
@@ -0,0 +1,28 @@
+--- !ruby/object:Gem::Specification
+name: dummy-gem-a
+version: !ruby/object:Gem::Version
+ version: 0.4.0
+platform: ruby
+authors:
+- "Nobody"
+date: 2008-10-03 00:00:00 -04:00
+files:
+- lib
+- lib/dummy-gem-a.rb
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+requirements: []
+specification_version: 2
+summary: Dummy Gem A
diff --git a/railties/test/vendor/gems/dummy-gem-a-0.4.0/lib/dummy-gem-a.rb b/railties/test/vendor/gems/dummy-gem-a-0.4.0/lib/dummy-gem-a.rb
new file mode 100644
index 0000000000..0453b38ab8
--- /dev/null
+++ b/railties/test/vendor/gems/dummy-gem-a-0.4.0/lib/dummy-gem-a.rb
@@ -0,0 +1 @@
+DUMMY_GEM_A_VERSION="0.4.0"
diff --git a/railties/test/vendor/gems/dummy-gem-b-0.4.0/.specification b/railties/test/vendor/gems/dummy-gem-b-0.4.0/.specification
new file mode 100644
index 0000000000..5ea692d7a1
--- /dev/null
+++ b/railties/test/vendor/gems/dummy-gem-b-0.4.0/.specification
@@ -0,0 +1,28 @@
+--- !ruby/object:Gem::Specification
+name: dummy-gem-b
+version: !ruby/object:Gem::Version
+ version: 0.4.0
+platform: ruby
+authors:
+- "Nobody"
+date: 2008-10-03 00:00:00 -04:00
+files:
+- lib
+- lib/dummy-gem-b.rb
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+requirements: []
+specification_version: 2
+summary: Dummy Gem B
diff --git a/railties/test/vendor/gems/dummy-gem-b-0.4.0/lib/dummy-gem-b.rb b/railties/test/vendor/gems/dummy-gem-b-0.4.0/lib/dummy-gem-b.rb
new file mode 100644
index 0000000000..850b5dda83
--- /dev/null
+++ b/railties/test/vendor/gems/dummy-gem-b-0.4.0/lib/dummy-gem-b.rb
@@ -0,0 +1 @@
+DUMMY_GEM_B_VERSION="0.4.0"
diff --git a/railties/test/vendor/gems/dummy-gem-b-0.6.0/.specification b/railties/test/vendor/gems/dummy-gem-b-0.6.0/.specification
new file mode 100644
index 0000000000..ab4707124a
--- /dev/null
+++ b/railties/test/vendor/gems/dummy-gem-b-0.6.0/.specification
@@ -0,0 +1,28 @@
+--- !ruby/object:Gem::Specification
+name: dummy-gem-b
+version: !ruby/object:Gem::Version
+ version: 0.6.0
+platform: ruby
+authors:
+- "Nobody"
+date: 2008-10-03 00:00:00 -04:00
+files:
+- lib
+- lib/dummy-gem-b.rb
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+requirements: []
+specification_version: 2
+summary: Dummy Gem B
diff --git a/railties/test/vendor/gems/dummy-gem-b-0.6.0/lib/dummy-gem-b.rb b/railties/test/vendor/gems/dummy-gem-b-0.6.0/lib/dummy-gem-b.rb
new file mode 100644
index 0000000000..7d6d01cd48
--- /dev/null
+++ b/railties/test/vendor/gems/dummy-gem-b-0.6.0/lib/dummy-gem-b.rb
@@ -0,0 +1 @@
+DUMMY_GEM_B_VERSION="0.6.0"
diff --git a/railties/test/vendor/gems/dummy-gem-c-0.4.0/.specification b/railties/test/vendor/gems/dummy-gem-c-0.4.0/.specification
new file mode 100644
index 0000000000..f90f60424c
--- /dev/null
+++ b/railties/test/vendor/gems/dummy-gem-c-0.4.0/.specification
@@ -0,0 +1,28 @@
+--- !ruby/object:Gem::Specification
+name: dummy-gem-c
+version: !ruby/object:Gem::Version
+ version: 0.4.0
+platform: ruby
+authors:
+- "Nobody"
+date: 2008-10-03 00:00:00 -04:00
+files:
+- lib
+- lib/dummy-gem-c.rb
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+requirements: []
+specification_version: 2
+summary: Dummy Gem C
diff --git a/railties/test/vendor/gems/dummy-gem-c-0.4.0/lib/dummy-gem-c.rb b/railties/test/vendor/gems/dummy-gem-c-0.4.0/lib/dummy-gem-c.rb
new file mode 100644
index 0000000000..1a416bef82
--- /dev/null
+++ b/railties/test/vendor/gems/dummy-gem-c-0.4.0/lib/dummy-gem-c.rb
@@ -0,0 +1 @@
+DUMMY_GEM_C_VERSION="0.4.0"
diff --git a/railties/test/vendor/gems/dummy-gem-c-0.6.0/.specification b/railties/test/vendor/gems/dummy-gem-c-0.6.0/.specification
new file mode 100644
index 0000000000..e75c0aa66a
--- /dev/null
+++ b/railties/test/vendor/gems/dummy-gem-c-0.6.0/.specification
@@ -0,0 +1,28 @@
+--- !ruby/object:Gem::Specification
+name: dummy-gem-c
+version: !ruby/object:Gem::Version
+ version: 0.6.0
+platform: ruby
+authors:
+- "Nobody"
+date: 2008-10-03 00:00:00 -04:00
+files:
+- lib
+- lib/dummy-gem-c.rb
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ version:
+requirements: []
+specification_version: 2
+summary: Dummy Gem C
diff --git a/railties/test/vendor/gems/dummy-gem-c-0.6.0/lib/dummy-gem-c.rb b/railties/test/vendor/gems/dummy-gem-c-0.6.0/lib/dummy-gem-c.rb
new file mode 100644
index 0000000000..9ba2ca8bb5
--- /dev/null
+++ b/railties/test/vendor/gems/dummy-gem-c-0.6.0/lib/dummy-gem-c.rb
@@ -0,0 +1 @@
+DUMMY_GEM_C_VERSION="0.6.0"