aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x[-rw-r--r--]Rakefile2
-rwxr-xr-x[-rw-r--r--]actionmailer/Rakefile2
-rwxr-xr-x[-rw-r--r--]actionpack/Rakefile2
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb6
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb20
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb11
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb64
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb5
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb1
-rw-r--r--actionpack/lib/action_view/template.rb5
-rw-r--r--actionpack/test/controller/mime_responds_test.rb39
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb46
-rw-r--r--actionpack/test/dispatch/request_test.rb10
-rw-r--r--actionpack/test/dispatch/routing_test.rb11
-rw-r--r--actionpack/test/lib/controller/fake_models.rb6
-rw-r--r--actionpack/test/template/lookup_context_test.rb29
-rwxr-xr-x[-rw-r--r--]activemodel/Rakefile0
-rw-r--r--activerecord/CHANGELOG11
-rwxr-xr-x[-rw-r--r--]activerecord/Rakefile2
-rw-r--r--activerecord/lib/active_record/associations.rb575
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb15
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency.rb225
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb278
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb34
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency/join_part.rb80
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb4
-rw-r--r--activerecord/lib/active_record/base.rb75
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb1
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb9
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb7
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb11
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb2
-rw-r--r--activerecord/test/cases/autosave_association_test.rb8
-rw-r--r--activerecord/test/cases/base_test.rb79
-rw-r--r--activerecord/test/cases/clone_test.rb33
-rw-r--r--activerecord/test/cases/dirty_test.rb8
-rw-r--r--activerecord/test/cases/dup_test.rb103
-rw-r--r--activerecord/test/cases/inheritance_test.rb2
-rw-r--r--activerecord/test/cases/locking_test.rb1
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb15
-rw-r--r--activerecord/test/cases/relations_test.rb9
-rw-r--r--activerecord/test/models/pet.rb8
-rwxr-xr-x[-rw-r--r--]activeresource/Rakefile2
-rwxr-xr-x[-rw-r--r--]activesupport/Rakefile0
-rw-r--r--activesupport/lib/active_support/core_ext/module/synchronization.rb1
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb7
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/module/synchronization_test.rb1
-rwxr-xr-x[-rw-r--r--]railties/Rakefile2
-rwxr-xr-x[-rw-r--r--]railties/lib/rails/generators/rails/app/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt2
-rwxr-xr-x[-rw-r--r--]railties/lib/rails/generators/rails/plugin_new/templates/Rakefile4
54 files changed, 1118 insertions, 771 deletions
diff --git a/Rakefile b/Rakefile
index 8c72312f28..1f3c770c77 100644..100755
--- a/Rakefile
+++ b/Rakefile
@@ -1,7 +1,7 @@
+#!/usr/bin/env rake
gem 'rdoc', '>= 2.5.10'
require 'rdoc'
-require 'rake'
require 'rdoc/task'
require 'net/http'
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index cba6e93c8a..123ef9bbbf 100644..100755
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -1,4 +1,4 @@
-require 'rake'
+#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rake/gempackagetask'
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index a6ce08113f..9030db9f7a 100644..100755
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -1,4 +1,4 @@
-require 'rake'
+#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rake/gempackagetask'
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 6fe1f4ece5..9ba37134b8 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -258,9 +258,8 @@ module ActionController #:nodoc:
# nil if :not_acceptable was sent to the client.
#
def retrieve_response_from_mimes(mimes=nil, &block)
- collector = Collector.new { default_render }
mimes ||= collect_mimes_from_class_level
- mimes.each { |mime| collector.send(mime) }
+ collector = Collector.new(mimes) { default_render }
block.call(collector) if block_given?
if format = request.negotiate_mime(collector.order)
@@ -277,8 +276,9 @@ module ActionController #:nodoc:
include AbstractController::Collector
attr_accessor :order
- def initialize(&block)
+ def initialize(mimes, &block)
@order, @responses, @default_response = [], {}, block
+ mimes.each { |mime| send(mime) }
end
def any(*args, &block)
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 67cf08445d..d6f6ab1855 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -55,7 +55,7 @@ module ActionController
end
add :json do |json, options|
- json = json.to_json(options) unless json.respond_to?(:to_str)
+ json = json.to_json(options) unless json.kind_of?(String)
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
self.content_type ||= Mime::JSON
self.response_body = json
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 2250cfa88a..08eab5634a 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -81,7 +81,7 @@ module Mime
class << self
- TRAILING_STAR_REGEXP = /(\w+)\/\*/
+ TRAILING_STAR_REGEXP = /(text|application)\/\*/
def lookup(string)
LOOKUP[string]
@@ -120,7 +120,11 @@ module Mime
params, q = header.split(/;\s*q=/)
if params
params.strip!
- list << AcceptItem.new(index, params, q) unless params.empty?
+ if params =~ TRAILING_STAR_REGEXP
+ parse_data_with_trailing_star($1).each { |m| list << AcceptItem.new(index, m.to_s, q) }
+ else
+ list << AcceptItem.new(index, params, q) unless params.empty?
+ end
end
end
list.sort!
@@ -177,6 +181,18 @@ module Mime
keys = Mime::LOOKUP.keys.select{|k| k.include?(input)}
Mime::LOOKUP.values_at(*keys).uniq
end
+
+ # This method is opposite of register method.
+ #
+ # Usage:
+ #
+ # Mime::Type.unregister("text/x-mobile", :mobile)
+ def unregister(string, symbol)
+ EXTENSION_LOOKUP.delete(symbol.to_s)
+ LOOKUP.delete(string)
+ symbol = symbol.to_s.upcase.intern
+ Mime.module_eval { remove_const(symbol) if const_defined?(symbol) }
+ end
end
def initialize(string, symbol = nil, synonyms = [])
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 1f7633cbea..1e7054f381 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -52,8 +52,7 @@ module ActionDispatch
# Returns a \host:\port string for this request, such as "example.com" or
# "example.com:8080".
def host_with_port
- opt_port = optional_port ? ":#{optional_port}" : nil
- "#{host}#{opt_port}"
+ "#{host}#{port_string}"
end
# Returns the port number of this request as an integer.
@@ -80,12 +79,18 @@ module ActionDispatch
port == standard_port
end
- # Returns a \port suffix like "8080" if the \port number of this request
+ # Returns a number \port suffix like 8080 if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
def optional_port
standard_port? ? nil : port
end
+ # Returns a string \port suffix, including colon, like ":8080" if the \port
+ # number of this request is not the default HTTP \port 80 or HTTPS \port 443.
+ def port_string
+ standard_port? ? '' : ":#{port}"
+ end
+
def server_port
@env['SERVER_PORT'].to_i
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index fb1f0d3f0c..01826fcede 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -705,61 +705,61 @@ module ActionDispatch
end
private
- def scope_options
+ def scope_options #:nodoc:
@scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
end
- def merge_path_scope(parent, child)
+ def merge_path_scope(parent, child) #:nodoc:
Mapper.normalize_path("#{parent}/#{child}")
end
- def merge_shallow_path_scope(parent, child)
+ def merge_shallow_path_scope(parent, child) #:nodoc:
Mapper.normalize_path("#{parent}/#{child}")
end
- def merge_as_scope(parent, child)
+ def merge_as_scope(parent, child) #:nodoc:
parent ? "#{parent}_#{child}" : child
end
- def merge_shallow_prefix_scope(parent, child)
+ def merge_shallow_prefix_scope(parent, child) #:nodoc:
parent ? "#{parent}_#{child}" : child
end
- def merge_module_scope(parent, child)
+ def merge_module_scope(parent, child) #:nodoc:
parent ? "#{parent}/#{child}" : child
end
- def merge_controller_scope(parent, child)
+ def merge_controller_scope(parent, child) #:nodoc:
child
end
- def merge_path_names_scope(parent, child)
+ def merge_path_names_scope(parent, child) #:nodoc:
merge_options_scope(parent, child)
end
- def merge_constraints_scope(parent, child)
+ def merge_constraints_scope(parent, child) #:nodoc:
merge_options_scope(parent, child)
end
- def merge_defaults_scope(parent, child)
+ def merge_defaults_scope(parent, child) #:nodoc:
merge_options_scope(parent, child)
end
- def merge_blocks_scope(parent, child)
+ def merge_blocks_scope(parent, child) #:nodoc:
merged = parent ? parent.dup : []
merged << child if child
merged
end
- def merge_options_scope(parent, child)
+ def merge_options_scope(parent, child) #:nodoc:
(parent || {}).except(*override_keys(child)).merge(child)
end
- def merge_shallow_scope(parent, child)
+ def merge_shallow_scope(parent, child) #:nodoc:
child ? true : false
end
- def override_keys(child)
+ def override_keys(child) #:nodoc:
child.key?(:only) || child.key?(:except) ? [:only, :except] : []
end
end
@@ -1196,7 +1196,7 @@ module ActionDispatch
@scope[:scope_level_resource]
end
- def apply_common_behavior_for(method, resources, options, &block)
+ def apply_common_behavior_for(method, resources, options, &block) #:nodoc:
if resources.length > 1
resources.each { |r| send(method, r, options, &block) }
return true
@@ -1226,23 +1226,23 @@ module ActionDispatch
false
end
- def action_options?(options)
+ def action_options?(options) #:nodoc:
options[:only] || options[:except]
end
- def scope_action_options?
+ def scope_action_options? #:nodoc:
@scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except])
end
- def scope_action_options
+ def scope_action_options #:nodoc:
@scope[:options].slice(:only, :except)
end
- def resource_scope?
+ def resource_scope? #:nodoc:
[:resource, :resources].include?(@scope[:scope_level])
end
- def resource_method_scope?
+ def resource_method_scope? #:nodoc:
[:collection, :member, :new].include?(@scope[:scope_level])
end
@@ -1268,7 +1268,7 @@ module ActionDispatch
@scope[:scope_level_resource] = old_resource
end
- def resource_scope(resource)
+ def resource_scope(resource) #:nodoc:
with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
scope(parent_resource.resource_scope) do
yield
@@ -1276,30 +1276,30 @@ module ActionDispatch
end
end
- def nested_options
+ def nested_options #:nodoc:
{}.tap do |options|
options[:as] = parent_resource.member_name
options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint?
end
end
- def id_constraint?
+ def id_constraint? #:nodoc:
@scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp)
end
- def id_constraint
+ def id_constraint #:nodoc:
@scope[:constraints][:id]
end
- def canonical_action?(action, flag)
+ def canonical_action?(action, flag) #:nodoc:
flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
end
- def shallow_scoping?
+ def shallow_scoping? #:nodoc:
shallow? && @scope[:scope_level] == :member
end
- def path_for_action(action, path)
+ def path_for_action(action, path) #:nodoc:
prefix = shallow_scoping? ?
"#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
@@ -1310,11 +1310,11 @@ module ActionDispatch
end
end
- def action_path(name, path = nil)
+ def action_path(name, path = nil) #:nodoc:
path || @scope[:path_names][name.to_sym] || name.to_s
end
- def prefix_name_for_action(as, action)
+ def prefix_name_for_action(as, action) #:nodoc:
if as
as.to_s
elsif !canonical_action?(action, @scope[:scope_level])
@@ -1322,12 +1322,14 @@ module ActionDispatch
end
end
- def name_for_action(as, action)
+ def name_for_action(as, action) #:nodoc:
prefix = prefix_name_for_action(as, action)
prefix = Mapper.normalize_name(prefix) if prefix
name_prefix = @scope[:as]
if parent_resource
+ return nil if as.nil? && action.nil?
+
collection_name = parent_resource.collection_name
member_name = parent_resource.member_name
end
@@ -1352,7 +1354,7 @@ module ActionDispatch
end
end
- module Shorthand
+ module Shorthand #:nodoc:
def match(*args)
if args.size == 1 && args.last.is_a?(Hash)
options = args.pop
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 6c3fc5126a..d4db78a25a 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -119,8 +119,9 @@ module ActionDispatch
# to split the domain from the host.
# * <tt>:domain</tt> - Specifies the domain of the link, using the +tld_length+
# to split the subdomain from the host.
- # * <tt>:tld_length</tt> - Optionally specify the tld length (only used if :subdomain
- # or :domain are supplied).
+ # * <tt>:tld_length</tt> - Number of labels the TLD id composed of, only used if
+ # <tt>:subdomain</tt> or <tt>:domain</tt> are supplied. Defaults to
+ # <tt>ActionDispatch::Http::URL.tld_length</tt>, which in turn defaults to 1.
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
index 15f8e10b7f..e52797042f 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
@@ -79,7 +79,6 @@ module ActionView
sources.each do |source|
asset_file_path!(path_to_asset(source, false))
end
- return sources
end
def collect_asset_files(*path)
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 6c6d659246..831a19654e 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -184,6 +184,11 @@ module ActionView
end
end
+ # Used to store template data by template handlers.
+ def data
+ @data ||= {}
+ end
+
def inspect
@inspect ||=
if defined?(Rails.root)
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 0da051822a..82969b2979 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -169,9 +169,6 @@ end
class StarStarMimeControllerTest < ActionController::TestCase
tests StarStarMimeController
- def setup; super; end
- def teardown; super; end
-
def test_javascript_with_format
@request.accept = "text/javascript"
get :index, :format => 'js'
@@ -204,10 +201,8 @@ class RespondToControllerTest < ActionController::TestCase
def teardown
super
- Mime.module_eval { remove_const :IPHONE if const_defined?(:IPHONE) }
- Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) }
- Mime::LOOKUP.reject!{|key,_| key == 'text/x-mobile'}
- Mime::LOOKUP.reject!{|key,_| key == 'text/iphone'}
+ Mime::Type.unregister('text/x-mobile', :iphone)
+ Mime::Type.unregister('text/iphone', :mobile)
end
def test_html
@@ -604,6 +599,17 @@ class InheritedRespondWithController < RespondWithController
end
end
+class RenderJsonRespondWithController < RespondWithController
+ clear_respond_to
+ respond_to :json
+
+ def index
+ respond_with(resource) do |format|
+ format.json { render :json => RenderJsonTestException.new('boom') }
+ end
+ end
+end
+
class EmptyRespondWithController < ActionController::Base
def index
respond_with(Customer.new("david", 13))
@@ -620,10 +626,8 @@ class RespondWithControllerTest < ActionController::TestCase
def teardown
super
- Mime.module_eval { remove_const :IPHONE if const_defined?(:IPHONE) }
- Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) }
- Mime::LOOKUP.reject!{|key,_| key == 'text/x-mobile'}
- Mime::LOOKUP.reject!{|key,_| key == 'text/iphone'}
+ Mime::Type.unregister('text/x-mobile', :iphone)
+ Mime::Type.unregister('text/iphone', :mobile)
end
def test_using_resource
@@ -921,6 +925,13 @@ class RespondWithControllerTest < ActionController::TestCase
assert_equal "JSON", @response.body
end
+ def test_render_json_object_responds_to_str_still_produce_json
+ @controller = RenderJsonRespondWithController.new
+ @request.accept = "application/json"
+ get :index, :format => :json
+ assert_equal %Q{{"message":"boom","error":"RenderJsonTestException"}}, @response.body
+ end
+
def test_no_double_render_is_raised
@request.accept = "text/html"
assert_raise ActionView::MissingTemplate do
@@ -1009,10 +1020,8 @@ class MimeControllerLayoutsTest < ActionController::TestCase
def teardown
super
- Mime.module_eval { remove_const :IPHONE if const_defined?(:IPHONE) }
- Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) }
- Mime::LOOKUP.reject!{|key,_| key == 'text/x-mobile'}
- Mime::LOOKUP.reject!{|key,_| key == 'text/iphone'}
+ Mime::Type.unregister('text/x-mobile', :iphone)
+ Mime::Type.unregister('text/iphone', :mobile)
end
def test_missing_layout_renders_properly
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 43123e68b2..9424d88498 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -12,6 +12,43 @@ class MimeTypeTest < ActiveSupport::TestCase
end
end
+ test "unregister" do
+ begin
+ Mime::Type.register("text/x-mobile", :mobile)
+ assert defined?(Mime::MOBILE)
+ assert_equal Mime::MOBILE, Mime::LOOKUP['text/x-mobile']
+ assert_equal Mime::MOBILE, Mime::EXTENSION_LOOKUP['mobile']
+
+ Mime::Type.unregister("text/x-mobile", :mobile)
+ assert !defined?(Mime::MOBILE), "Mime::MOBILE should not be defined"
+ assert !Mime::LOOKUP.has_key?('text/x-mobile'), "Mime::LOOKUP should not have key ['text/x-mobile]"
+ assert !Mime::EXTENSION_LOOKUP.has_key?('mobile'), "Mime::EXTENSION_LOOKUP should not have key ['mobile]"
+ ensure
+ Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) }
+ Mime::LOOKUP.reject!{|key,_| key == 'text/x-mobile'}
+ end
+ end
+
+ test "parse text with trailing star at the beginning" do
+ accept = "text/*, text/html, application/json, multipart/form-data"
+ expect = [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::TEXT, Mime::YAML, Mime::JS, Mime::MULTIPART_FORM]
+ parsed = Mime::Type.parse(accept)
+ assert_equal expect.size, parsed.size
+ Range.new(0,expect.size-1).to_a.each do |index|
+ assert_equal expect[index], parsed[index], "Failed for index number #{index}"
+ end
+ end
+
+ test "parse text with trailing star in the end" do
+ accept = "text/html, application/json, multipart/form-data, text/*"
+ expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::XML, Mime::ICS, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]
+ parsed = Mime::Type.parse(accept)
+ assert_equal 10, parsed.size
+ Range.new(0,expect.size-1).to_a.each do |index|
+ assert_equal expect[index], parsed[index], "Failed for index number #{index}"
+ end
+ end
+
test "parse text with trailing star" do
accept = "text/*"
expect = [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT].sort_by(&:to_s)
@@ -28,12 +65,6 @@ class MimeTypeTest < ActiveSupport::TestCase
assert_equal expect, parsed.sort_by(&:to_s)
end
- test "parse image with trailing star" do
- accept = "image/*"
- parsed = Mime::Type.parse(accept)
- assert parsed.include?(Mime::PNG)
- end
-
test "parse without q" do
accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,application/pdf,*/*"
expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::TEXT, Mime::PDF, Mime::ALL]
@@ -68,8 +99,7 @@ class MimeTypeTest < ActiveSupport::TestCase
assert_equal Mime::GIF, Mime::SET.last
end
ensure
- Mime.module_eval { remove_const :GIF if const_defined?(:GIF) }
- Mime::LOOKUP.reject!{|key,_| key == 'image/gif'}
+ Mime::Type.unregister('image/gif', :gif)
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index d140ea8358..8f672c1149 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -172,6 +172,14 @@ class RequestTest < ActiveSupport::TestCase
assert_equal 8080, request.optional_port
end
+ test "port string" do
+ request = stub_request 'HTTP_HOST' => 'www.example.org:80'
+ assert_equal '', request.port_string
+
+ request = stub_request 'HTTP_HOST' => 'www.example.org:8080'
+ assert_equal ':8080', request.port_string
+ end
+
test "full path" do
request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/path/of/some/uri', 'QUERY_STRING' => 'mapped=1'
assert_equal "/path/of/some/uri?mapped=1", request.fullpath
@@ -392,7 +400,7 @@ class RequestTest < ActiveSupport::TestCase
mock_rack_env = { "QUERY_STRING" => "x[y]=1&x[y][][w]=2", "rack.input" => "foo" }
request = nil
begin
- request = stub_request(mock_rack_env)
+ request = stub_request(mock_rack_env)
request.parameters
rescue TypeError => e
# rack will raise a TypeError when parsing this query string
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 0ac8c249cb..bbd010ea6d 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -155,6 +155,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
resources :replies do
+ collection do
+ get 'page/:page' => 'replies#index', :page => %r{\d+}
+ get ':page' => 'replies#index', :page => %r{\d+}
+ end
+
new do
post :preview
end
@@ -1241,6 +1246,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper
+ with_test_routes do
+ assert_equal '/replies', replies_path
+ end
+ end
+
def test_scoped_controller_with_namespace_and_action
with_test_routes do
assert_equal '/account/twitter/callback', account_callback_path("twitter")
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index ae0c38184d..bd18cdc1b8 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -194,3 +194,9 @@ class ArelLike
a.each { |i| yield i }
end
end
+
+class RenderJsonTestException < Exception
+ def to_json(options = nil)
+ return { :error => self.class.name, :message => self.to_str }.to_json
+ end
+end
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index 6d3b26e131..c9dd27cf2a 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -180,6 +180,16 @@ class LookupContextTest < ActiveSupport::TestCase
assert_not_equal template, old_template
end
+
+ test "data can be stored in cached templates" do
+ template = @lookup_context.find("hello_world", "test")
+ template.data["cached"] = "data"
+ assert_equal "Hello world!", template.source
+
+ template = @lookup_context.find("hello_world", "test")
+ assert_equal "data", template.data["cached"]
+ assert_equal "Hello world!", template.source
+ end
end
class LookupContextWithFalseCaching < ActiveSupport::TestCase
@@ -205,7 +215,7 @@ class LookupContextWithFalseCaching < ActiveSupport::TestCase
assert_equal "Bar", template.source
end
- test "if no template was found in the second lookup, give it higher preference" do
+ test "if no template was found in the second lookup, with no cache, raise error" do
template = @lookup_context.find("foo", "test", true)
assert_equal "Foo", template.source
@@ -215,7 +225,7 @@ class LookupContextWithFalseCaching < ActiveSupport::TestCase
end
end
- test "if no template was cached in the first lookup, do not use the cache in the second" do
+ test "if no template was cached in the first lookup, retrieval should work in the second call" do
@resolver.hash.clear
assert_raise ActionView::MissingTemplate do
@lookup_context.find("foo", "test", true)
@@ -225,4 +235,19 @@ class LookupContextWithFalseCaching < ActiveSupport::TestCase
template = @lookup_context.find("foo", "test", true)
assert_equal "Foo", template.source
end
+
+ test "data can be stored as long as template was not updated" do
+ template = @lookup_context.find("foo", "test", true)
+ template.data["cached"] = "data"
+ assert_equal "Foo", template.source
+
+ template = @lookup_context.find("foo", "test", true)
+ assert_equal "data", template.data["cached"]
+ assert_equal "Foo", template.source
+
+ @resolver.hash["test/_foo.erb"][1] = Time.now.utc
+ template = @lookup_context.find("foo", "test", true)
+ assert_nil template.data["cached"]
+ assert_equal "Foo", template.source
+ end
end \ No newline at end of file
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index 0372c7a03e..0372c7a03e 100644..100755
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index a3e3051b96..f46db909ba 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,16 @@
*Rails 3.1.0 (unreleased)*
+* ActiveRecord::Base#dup and ActiveRecord::Base#clone semantics have changed
+to closer match normal Ruby dup and clone semantics.
+
+* Calling ActiveRecord::Base#clone will result in a shallow copy of the record,
+including copying the frozen state. No callbacks will be called.
+
+* Calling ActiveRecord::Base#dup will duplicate the record, including calling
+after initialize hooks. Frozen state will not be copied, and all associations
+will be cleared. A duped record will return true for new_record?, have a nil
+id field, and is saveable.
+
* Migrations can be defined as reversible, meaning that the migration system
will figure out how to reverse your migration. To use reversible migrations,
just define the "change" method. For example:
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 395c72dfbc..f9b77c1799 100644..100755
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,4 +1,4 @@
-require 'rake'
+#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rake/gempackagetask'
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 7da38cd03f..0d9171d876 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -5,6 +5,7 @@ require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/conversions'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/class/attribute'
+require 'active_record/associations/class_methods/join_dependency'
module ActiveRecord
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -1832,580 +1833,6 @@ module ActiveRecord
Array.wrap(extensions)
end
end
-
- class JoinDependency # :nodoc:
- attr_reader :join_parts, :reflections, :table_aliases
-
- def initialize(base, associations, joins)
- @join_parts = [JoinBase.new(base, joins)]
- @associations = {}
- @reflections = []
- @table_aliases = Hash.new(0)
- @table_aliases[base.table_name] = 1
- build(associations)
- end
-
- def graft(*associations)
- associations.each do |association|
- join_associations.detect {|a| association == a} ||
- build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
- end
- self
- end
-
- def join_associations
- join_parts.last(join_parts.length - 1)
- end
-
- def join_base
- join_parts.first
- end
-
- def count_aliases_from_table_joins(name)
- # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
- quoted_name = join_base.active_record.connection.quote_table_name(name.downcase).downcase
- join_sql = join_base.table_joins.to_s.downcase
- join_sql.blank? ? 0 :
- # Table names
- join_sql.scan(/join(?:\s+\w+)?\s+#{quoted_name}\son/).size +
- # Table aliases
- join_sql.scan(/join(?:\s+\w+)?\s+\S+\s+#{quoted_name}\son/).size
- end
-
- def instantiate(rows)
- primary_key = join_base.aliased_primary_key
- parents = {}
-
- records = rows.map { |model|
- primary_id = model[primary_key]
- parent = parents[primary_id] ||= join_base.instantiate(model)
- construct(parent, @associations, join_associations.dup, model)
- parent
- }.uniq
-
- remove_duplicate_results!(join_base.active_record, records, @associations)
- records
- end
-
- def remove_duplicate_results!(base, records, associations)
- case associations
- when Symbol, String
- reflection = base.reflections[associations]
- remove_uniq_by_reflection(reflection, records)
- when Array
- associations.each do |association|
- remove_duplicate_results!(base, records, association)
- end
- when Hash
- associations.keys.each do |name|
- reflection = base.reflections[name]
- remove_uniq_by_reflection(reflection, records)
-
- parent_records = []
- records.each do |record|
- if descendant = record.send(reflection.name)
- if reflection.collection?
- parent_records.concat descendant.target.uniq
- else
- parent_records << descendant
- end
- end
- end
-
- remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
- end
- end
- end
-
- protected
-
- def cache_joined_association(association)
- associations = []
- parent = association.parent
- while parent != join_base
- associations.unshift(parent.reflection.name)
- parent = parent.parent
- end
- ref = @associations
- associations.each do |key|
- ref = ref[key]
- end
- ref[association.reflection.name] ||= {}
- end
-
- def build(associations, parent = nil, join_type = Arel::InnerJoin)
- parent ||= join_parts.last
- case associations
- when Symbol, String
- reflection = parent.reflections[associations.to_s.intern] or
- raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
- unless join_association = find_join_association(reflection, parent)
- @reflections << reflection
- join_association = build_join_association(reflection, parent)
- join_association.join_type = join_type
- @join_parts << join_association
- cache_joined_association(join_association)
- end
- join_association
- when Array
- associations.each do |association|
- build(association, parent, join_type)
- end
- when Hash
- associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
- join_association = build(name, parent, join_type)
- build(associations[name], join_association, join_type)
- end
- else
- raise ConfigurationError, associations.inspect
- end
- end
-
- def find_join_association(name_or_reflection, parent)
- if String === name_or_reflection
- name_or_reflection = name_or_reflection.to_sym
- end
-
- join_associations.detect { |j|
- j.reflection == name_or_reflection && j.parent == parent
- }
- end
-
- def remove_uniq_by_reflection(reflection, records)
- if reflection && reflection.collection?
- records.each { |record| record.send(reflection.name).target.uniq! }
- end
- end
-
- def build_join_association(reflection, parent)
- JoinAssociation.new(reflection, self, parent)
- end
-
- def construct(parent, associations, join_parts, row)
- case associations
- when Symbol, String
- name = associations.to_s
-
- join_part = join_parts.detect { |j|
- j.reflection.name.to_s == name &&
- j.parent_table_name == parent.class.table_name }
-
- raise(ConfigurationError, "No such association") unless join_part
-
- join_parts.delete(join_part)
- construct_association(parent, join_part, row)
- when Array
- associations.each do |association|
- construct(parent, association, join_parts, row)
- end
- when Hash
- associations.sort_by { |k,_| k.to_s }.each do |name, assoc|
- association = construct(parent, name, join_parts, row)
- construct(association, assoc, join_parts, row) if association
- end
- else
- raise ConfigurationError, associations.inspect
- end
- end
-
- def construct_association(record, join_part, row)
- return if record.id.to_s != join_part.parent.record_id(row).to_s
-
- macro = join_part.reflection.macro
- if macro == :has_one
- return if record.instance_variable_defined?("@#{join_part.reflection.name}")
- association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
- set_target_and_inverse(join_part, association, record)
- else
- return if row[join_part.aliased_primary_key].nil?
- association = join_part.instantiate(row)
- case macro
- when :has_many, :has_and_belongs_to_many
- collection = record.send(join_part.reflection.name)
- collection.loaded
- collection.target.push(association)
- collection.__send__(:set_inverse_instance, association, record)
- when :belongs_to
- set_target_and_inverse(join_part, association, record)
- else
- raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
- end
- end
- association
- end
-
- def set_target_and_inverse(join_part, association, record)
- association_proxy = record.send("set_#{join_part.reflection.name}_target", association)
- association_proxy.__send__(:set_inverse_instance, association, record)
- end
-
- # A JoinPart represents a part of a JoinDependency. It is an abstract class, inherited
- # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
- # everything else is being joined onto. A JoinAssociation represents an association which
- # is joining to the base. A JoinAssociation may result in more than one actual join
- # operations (for example a has_and_belongs_to_many JoinAssociation would result in
- # two; one for the join table and one for the target table).
- class JoinPart # :nodoc:
- # The Active Record class which this join part is associated 'about'; for a JoinBase
- # this is the actual base model, for a JoinAssociation this is the target model of the
- # association.
- attr_reader :active_record
-
- delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :arel_engine, :to => :active_record
-
- def initialize(active_record)
- @active_record = active_record
- @cached_record = {}
- end
-
- def ==(other)
- raise NotImplementedError
- end
-
- # An Arel::Table for the active_record
- def table
- raise NotImplementedError
- end
-
- # The prefix to be used when aliasing columns in the active_record's table
- def aliased_prefix
- raise NotImplementedError
- end
-
- # The alias for the active_record's table
- def aliased_table_name
- raise NotImplementedError
- end
-
- # The alias for the primary key of the active_record's table
- def aliased_primary_key
- "#{aliased_prefix}_r0"
- end
-
- # An array of [column_name, alias] pairs for the table
- def column_names_with_alias
- unless defined?(@column_names_with_alias)
- @column_names_with_alias = []
-
- ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
- @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
- end
- end
-
- @column_names_with_alias
- end
-
- def extract_record(row)
- Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
- end
-
- def record_id(row)
- row[aliased_primary_key]
- end
-
- def instantiate(row)
- @cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
- end
- end
-
- class JoinBase < JoinPart # :nodoc:
- # Extra joins provided when the JoinDependency was created
- attr_reader :table_joins
-
- def initialize(active_record, joins = nil)
- super(active_record)
- @table_joins = joins
- end
-
- def ==(other)
- other.class == self.class &&
- other.active_record == active_record
- end
-
- def aliased_prefix
- "t0"
- end
-
- def table
- Arel::Table.new(table_name, :engine => arel_engine, :columns => active_record.columns)
- end
-
- def aliased_table_name
- active_record.table_name
- end
- end
-
- class JoinAssociation < JoinPart # :nodoc:
- # The reflection of the association represented
- attr_reader :reflection
-
- # The JoinDependency object which this JoinAssociation exists within. This is mainly
- # relevant for generating aliases which do not conflict with other joins which are
- # part of the query.
- attr_reader :join_dependency
-
- # A JoinBase instance representing the active record we are joining onto.
- # (So in Author.has_many :posts, the Author would be that base record.)
- attr_reader :parent
-
- # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
- attr_accessor :join_type
-
- # These implement abstract methods from the superclass
- attr_reader :aliased_prefix, :aliased_table_name
-
- delegate :options, :through_reflection, :source_reflection, :to => :reflection
- delegate :table, :table_name, :to => :parent, :prefix => true
-
- def initialize(reflection, join_dependency, parent = nil)
- reflection.check_validity!
-
- if reflection.options[:polymorphic]
- raise EagerLoadPolymorphicError.new(reflection)
- end
-
- super(reflection.klass)
-
- @reflection = reflection
- @join_dependency = join_dependency
- @parent = parent
- @join_type = Arel::InnerJoin
-
- # This must be done eagerly upon initialisation because the alias which is produced
- # depends on the state of the join dependency, but we want it to work the same way
- # every time.
- allocate_aliases
- end
-
- def ==(other)
- other.class == self.class &&
- other.reflection == reflection &&
- other.parent == parent
- end
-
- def find_parent_in(other_join_dependency)
- other_join_dependency.join_parts.detect do |join_part|
- self.parent == join_part
- end
- end
-
- def join_to(relation)
- send("join_#{reflection.macro}_to", relation)
- end
-
- def join_relation(joining_relation)
- self.join_type = Arel::OuterJoin
- joining_relation.joins(self)
- end
-
- def table
- @table ||= Arel::Table.new(
- table_name, :as => aliased_table_name,
- :engine => arel_engine, :columns => active_record.columns
- )
- end
-
- # More semantic name given we are talking about associations
- alias_method :target_table, :table
-
- protected
-
- def aliased_table_name_for(name, suffix = nil)
- if @join_dependency.table_aliases[name].zero?
- @join_dependency.table_aliases[name] = @join_dependency.count_aliases_from_table_joins(name)
- end
-
- if !@join_dependency.table_aliases[name].zero? # We need an alias
- name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
- @join_dependency.table_aliases[name] += 1
- if @join_dependency.table_aliases[name] == 1 # First time we've seen this name
- # Also need to count the aliases from the table_aliases to avoid incorrect count
- @join_dependency.table_aliases[name] += @join_dependency.count_aliases_from_table_joins(name)
- end
- table_index = @join_dependency.table_aliases[name]
- name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index}" if table_index > 1
- else
- @join_dependency.table_aliases[name] += 1
- end
-
- name
- end
-
- def pluralize(table_name)
- ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
- end
-
- def interpolate_sql(sql)
- instance_eval("%@#{sql.gsub('@', '\@')}@", __FILE__, __LINE__)
- end
-
- private
-
- def allocate_aliases
- @aliased_prefix = "t#{ join_dependency.join_parts.size }"
- @aliased_table_name = aliased_table_name_for(table_name)
-
- if reflection.macro == :has_and_belongs_to_many
- @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
- elsif [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
- @aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
- end
- end
-
- def process_conditions(conditions, table_name)
- Arel.sql(interpolate_sql(sanitize_sql(conditions, table_name)))
- end
-
- def join_target_table(relation, *conditions)
- relation = relation.join(target_table, join_type)
-
- # If the target table is an STI model then we must be sure to only include records of
- # its type and its sub-types.
- unless active_record.descends_from_active_record?
- sti_column = target_table[active_record.inheritance_column]
-
- sti_condition = sti_column.eq(active_record.sti_name)
- active_record.descendants.each do |subclass|
- sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name))
- end
-
- conditions << sti_condition
- end
-
- # If the reflection has conditions, add them
- if options[:conditions]
- conditions << process_conditions(options[:conditions], aliased_table_name)
- end
-
- relation = relation.on(*conditions)
- end
-
- def join_has_and_belongs_to_many_to(relation)
- join_table = Arel::Table.new(
- options[:join_table], :engine => arel_engine,
- :as => @aliased_join_table_name
- )
-
- fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
- klass_fk = options[:association_foreign_key] || reflection.klass.to_s.foreign_key
-
- relation = relation.join(join_table, join_type)
- relation = relation.on(
- join_table[fk].
- eq(parent_table[reflection.active_record.primary_key])
- )
-
- join_target_table(
- relation,
- target_table[reflection.klass.primary_key].
- eq(join_table[klass_fk])
- )
- end
-
- def join_has_many_to(relation)
- if reflection.options[:through]
- join_has_many_through_to(relation)
- elsif reflection.options[:as]
- join_has_many_polymorphic_to(relation)
- else
- foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
- primary_key = options[:primary_key] || parent.primary_key
-
- join_target_table(
- relation,
- target_table[foreign_key].
- eq(parent_table[primary_key])
- )
- end
- end
- alias :join_has_one_to :join_has_many_to
-
- def join_has_many_through_to(relation)
- join_table = Arel::Table.new(
- through_reflection.klass.table_name, :engine => arel_engine,
- :as => @aliased_join_table_name
- )
-
- jt_conditions = []
- jt_foreign_key = first_key = second_key = nil
-
- if through_reflection.options[:as] # has_many :through against a polymorphic join
- as_key = through_reflection.options[:as].to_s
- jt_foreign_key = as_key + '_id'
-
- jt_conditions <<
- join_table[as_key + '_type'].
- eq(parent.active_record.base_class.name)
- else
- jt_foreign_key = through_reflection.primary_key_name
- end
-
- case source_reflection.macro
- when :has_many
- second_key = options[:foreign_key] || primary_key
-
- if source_reflection.options[:as]
- first_key = "#{source_reflection.options[:as]}_id"
- else
- first_key = through_reflection.klass.base_class.to_s.foreign_key
- end
-
- unless through_reflection.klass.descends_from_active_record?
- jt_conditions <<
- join_table[through_reflection.active_record.inheritance_column].
- eq(through_reflection.klass.sti_name)
- end
- when :belongs_to
- first_key = primary_key
-
- if reflection.options[:source_type]
- second_key = source_reflection.association_foreign_key
-
- jt_conditions <<
- join_table[reflection.source_reflection.options[:foreign_type]].
- eq(reflection.options[:source_type])
- else
- second_key = source_reflection.primary_key_name
- end
- end
-
- jt_conditions <<
- parent_table[parent.primary_key].
- eq(join_table[jt_foreign_key])
-
- if through_reflection.options[:conditions]
- jt_conditions << process_conditions(through_reflection.options[:conditions], aliased_table_name)
- end
-
- relation = relation.join(join_table, join_type).on(*jt_conditions)
-
- join_target_table(
- relation,
- target_table[first_key].eq(join_table[second_key])
- )
- end
-
- def join_has_many_polymorphic_to(relation)
- join_target_table(
- relation,
- target_table["#{reflection.options[:as]}_id"].
- eq(parent_table[parent.primary_key]),
- target_table["#{reflection.options[:as]}_type"].
- eq(parent.active_record.base_class.name)
- )
- end
-
- def join_belongs_to_to(relation)
- foreign_key = options[:foreign_key] || reflection.primary_key_name
- primary_key = options[:primary_key] || reflection.klass.primary_key
-
- join_target_table(
- relation,
- target_table[primary_key].eq(parent_table[foreign_key])
- )
- end
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 774103342a..ba9373ba6a 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -235,12 +235,12 @@ module ActiveRecord
# Removes all records from this association. Returns +self+ so method calls may be chained.
def clear
- return self if length.zero? # forces load_target if it hasn't happened already
-
- if @reflection.options[:dependent] && @reflection.options[:dependent] == :destroy
- destroy_all
- else
- delete_all
+ unless length.zero? # forces load_target if it hasn't happened already
+ if @reflection.options[:dependent] == :destroy
+ destroy_all
+ else
+ delete_all
+ end
end
self
@@ -357,8 +357,7 @@ module ActiveRecord
return false unless record.is_a?(@reflection.klass)
return include_in_memory?(record) unless record.persisted?
load_target if @reflection.options[:finder_sql] && !loaded?
- return @target.include?(record) if loaded?
- exists?(record)
+ loaded? ? @target.include?(record) : exists?(record)
end
def proxy_respond_to?(method, include_private = false)
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
new file mode 100644
index 0000000000..6ab7bd0b06
--- /dev/null
+++ b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
@@ -0,0 +1,225 @@
+require 'active_record/associations/class_methods/join_dependency/join_part'
+require 'active_record/associations/class_methods/join_dependency/join_base'
+require 'active_record/associations/class_methods/join_dependency/join_association'
+
+module ActiveRecord
+ module Associations
+ module ClassMethods
+ class JoinDependency # :nodoc:
+ attr_reader :join_parts, :reflections, :table_aliases
+
+ def initialize(base, associations, joins)
+ @join_parts = [JoinBase.new(base, joins)]
+ @associations = {}
+ @reflections = []
+ @table_aliases = Hash.new(0)
+ @table_aliases[base.table_name] = 1
+ build(associations)
+ end
+
+ def graft(*associations)
+ associations.each do |association|
+ join_associations.detect {|a| association == a} ||
+ build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
+ end
+ self
+ end
+
+ def join_associations
+ join_parts.last(join_parts.length - 1)
+ end
+
+ def join_base
+ join_parts.first
+ end
+
+ def columns
+ join_parts.collect { |join_part|
+ table = join_part.aliased_table
+ join_part.column_names_with_alias.collect{ |column_name, aliased_name|
+ table[column_name].as Arel.sql(aliased_name)
+ }
+ }.flatten
+ end
+
+ def count_aliases_from_table_joins(name)
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
+ quoted_name = join_base.active_record.connection.quote_table_name(name.downcase).downcase
+ join_sql = join_base.table_joins.to_s.downcase
+ join_sql.blank? ? 0 :
+ # Table names
+ join_sql.scan(/join(?:\s+\w+)?\s+#{quoted_name}\son/).size +
+ # Table aliases
+ join_sql.scan(/join(?:\s+\w+)?\s+\S+\s+#{quoted_name}\son/).size
+ end
+
+ def instantiate(rows)
+ primary_key = join_base.aliased_primary_key
+ parents = {}
+
+ records = rows.map { |model|
+ primary_id = model[primary_key]
+ parent = parents[primary_id] ||= join_base.instantiate(model)
+ construct(parent, @associations, join_associations.dup, model)
+ parent
+ }.uniq
+
+ remove_duplicate_results!(join_base.active_record, records, @associations)
+ records
+ end
+
+ def remove_duplicate_results!(base, records, associations)
+ case associations
+ when Symbol, String
+ reflection = base.reflections[associations]
+ remove_uniq_by_reflection(reflection, records)
+ when Array
+ associations.each do |association|
+ remove_duplicate_results!(base, records, association)
+ end
+ when Hash
+ associations.keys.each do |name|
+ reflection = base.reflections[name]
+ remove_uniq_by_reflection(reflection, records)
+
+ parent_records = []
+ records.each do |record|
+ if descendant = record.send(reflection.name)
+ if reflection.collection?
+ parent_records.concat descendant.target.uniq
+ else
+ parent_records << descendant
+ end
+ end
+ end
+
+ remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
+ end
+ end
+ end
+
+ protected
+
+ def cache_joined_association(association)
+ associations = []
+ parent = association.parent
+ while parent != join_base
+ associations.unshift(parent.reflection.name)
+ parent = parent.parent
+ end
+ ref = @associations
+ associations.each do |key|
+ ref = ref[key]
+ end
+ ref[association.reflection.name] ||= {}
+ end
+
+ def build(associations, parent = nil, join_type = Arel::InnerJoin)
+ parent ||= join_parts.last
+ case associations
+ when Symbol, String
+ reflection = parent.reflections[associations.to_s.intern] or
+ raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
+ unless join_association = find_join_association(reflection, parent)
+ @reflections << reflection
+ join_association = build_join_association(reflection, parent)
+ join_association.join_type = join_type
+ @join_parts << join_association
+ cache_joined_association(join_association)
+ end
+ join_association
+ when Array
+ associations.each do |association|
+ build(association, parent, join_type)
+ end
+ when Hash
+ associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
+ join_association = build(name, parent, join_type)
+ build(associations[name], join_association, join_type)
+ end
+ else
+ raise ConfigurationError, associations.inspect
+ end
+ end
+
+ def find_join_association(name_or_reflection, parent)
+ if String === name_or_reflection
+ name_or_reflection = name_or_reflection.to_sym
+ end
+
+ join_associations.detect { |j|
+ j.reflection == name_or_reflection && j.parent == parent
+ }
+ end
+
+ def remove_uniq_by_reflection(reflection, records)
+ if reflection && reflection.collection?
+ records.each { |record| record.send(reflection.name).target.uniq! }
+ end
+ end
+
+ def build_join_association(reflection, parent)
+ JoinAssociation.new(reflection, self, parent)
+ end
+
+ def construct(parent, associations, join_parts, row)
+ case associations
+ when Symbol, String
+ name = associations.to_s
+
+ join_part = join_parts.detect { |j|
+ j.reflection.name.to_s == name &&
+ j.parent_table_name == parent.class.table_name }
+
+ raise(ConfigurationError, "No such association") unless join_part
+
+ join_parts.delete(join_part)
+ construct_association(parent, join_part, row)
+ when Array
+ associations.each do |association|
+ construct(parent, association, join_parts, row)
+ end
+ when Hash
+ associations.sort_by { |k,_| k.to_s }.each do |name, assoc|
+ association = construct(parent, name, join_parts, row)
+ construct(association, assoc, join_parts, row) if association
+ end
+ else
+ raise ConfigurationError, associations.inspect
+ end
+ end
+
+ def construct_association(record, join_part, row)
+ return if record.id.to_s != join_part.parent.record_id(row).to_s
+
+ macro = join_part.reflection.macro
+ if macro == :has_one
+ return if record.instance_variable_defined?("@#{join_part.reflection.name}")
+ association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
+ set_target_and_inverse(join_part, association, record)
+ else
+ return if row[join_part.aliased_primary_key].nil?
+ association = join_part.instantiate(row)
+ case macro
+ when :has_many, :has_and_belongs_to_many
+ collection = record.send(join_part.reflection.name)
+ collection.loaded
+ collection.target.push(association)
+ collection.__send__(:set_inverse_instance, association, record)
+ when :belongs_to
+ set_target_and_inverse(join_part, association, record)
+ else
+ raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
+ end
+ end
+ association
+ end
+
+ def set_target_and_inverse(join_part, association, record)
+ association_proxy = record.send("set_#{join_part.reflection.name}_target", association)
+ association_proxy.__send__(:set_inverse_instance, association, record)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
new file mode 100644
index 0000000000..5e5c01c77a
--- /dev/null
+++ b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
@@ -0,0 +1,278 @@
+module ActiveRecord
+ module Associations
+ module ClassMethods
+ class JoinDependency # :nodoc:
+ class JoinAssociation < JoinPart # :nodoc:
+ # The reflection of the association represented
+ attr_reader :reflection
+
+ # The JoinDependency object which this JoinAssociation exists within. This is mainly
+ # relevant for generating aliases which do not conflict with other joins which are
+ # part of the query.
+ attr_reader :join_dependency
+
+ # A JoinBase instance representing the active record we are joining onto.
+ # (So in Author.has_many :posts, the Author would be that base record.)
+ attr_reader :parent
+
+ # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
+ attr_accessor :join_type
+
+ # These implement abstract methods from the superclass
+ attr_reader :aliased_prefix, :aliased_table_name
+
+ delegate :options, :through_reflection, :source_reflection, :to => :reflection
+ delegate :table, :table_name, :to => :parent, :prefix => true
+
+ def initialize(reflection, join_dependency, parent = nil)
+ reflection.check_validity!
+
+ if reflection.options[:polymorphic]
+ raise EagerLoadPolymorphicError.new(reflection)
+ end
+
+ super(reflection.klass)
+
+ @reflection = reflection
+ @join_dependency = join_dependency
+ @parent = parent
+ @join_type = Arel::InnerJoin
+
+ # This must be done eagerly upon initialisation because the alias which is produced
+ # depends on the state of the join dependency, but we want it to work the same way
+ # every time.
+ allocate_aliases
+ end
+
+ def ==(other)
+ other.class == self.class &&
+ other.reflection == reflection &&
+ other.parent == parent
+ end
+
+ def find_parent_in(other_join_dependency)
+ other_join_dependency.join_parts.detect do |join_part|
+ self.parent == join_part
+ end
+ end
+
+ def join_to(relation)
+ send("join_#{reflection.macro}_to", relation)
+ end
+
+ def join_relation(joining_relation)
+ self.join_type = Arel::OuterJoin
+ joining_relation.joins(self)
+ end
+
+ def table
+ @table ||= Arel::Table.new(
+ table_name, :as => aliased_table_name,
+ :engine => arel_engine, :columns => active_record.columns
+ )
+ end
+
+ # More semantic name given we are talking about associations
+ alias_method :target_table, :table
+
+ protected
+
+ def aliased_table_name_for(name, suffix = nil)
+ if @join_dependency.table_aliases[name].zero?
+ @join_dependency.table_aliases[name] = @join_dependency.count_aliases_from_table_joins(name)
+ end
+
+ if !@join_dependency.table_aliases[name].zero? # We need an alias
+ name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
+ @join_dependency.table_aliases[name] += 1
+ if @join_dependency.table_aliases[name] == 1 # First time we've seen this name
+ # Also need to count the aliases from the table_aliases to avoid incorrect count
+ @join_dependency.table_aliases[name] += @join_dependency.count_aliases_from_table_joins(name)
+ end
+ table_index = @join_dependency.table_aliases[name]
+ name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index}" if table_index > 1
+ else
+ @join_dependency.table_aliases[name] += 1
+ end
+
+ name
+ end
+
+ def pluralize(table_name)
+ ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
+ end
+
+ def interpolate_sql(sql)
+ instance_eval("%@#{sql.gsub('@', '\@')}@", __FILE__, __LINE__)
+ end
+
+ private
+
+ def allocate_aliases
+ @aliased_prefix = "t#{ join_dependency.join_parts.size }"
+ @aliased_table_name = aliased_table_name_for(table_name)
+
+ if reflection.macro == :has_and_belongs_to_many
+ @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
+ elsif [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
+ @aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
+ end
+ end
+
+ def process_conditions(conditions, table_name)
+ Arel.sql(interpolate_sql(sanitize_sql(conditions, table_name)))
+ end
+
+ def join_target_table(relation, *conditions)
+ relation = relation.join(target_table, join_type)
+
+ # If the target table is an STI model then we must be sure to only include records of
+ # its type and its sub-types.
+ unless active_record.descends_from_active_record?
+ sti_column = target_table[active_record.inheritance_column]
+
+ sti_condition = sti_column.eq(active_record.sti_name)
+ active_record.descendants.each do |subclass|
+ sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name))
+ end
+
+ conditions << sti_condition
+ end
+
+ # If the reflection has conditions, add them
+ if options[:conditions]
+ conditions << process_conditions(options[:conditions], aliased_table_name)
+ end
+
+ relation = relation.on(*conditions)
+ end
+
+ def join_has_and_belongs_to_many_to(relation)
+ join_table = Arel::Table.new(
+ options[:join_table], :engine => arel_engine,
+ :as => @aliased_join_table_name
+ )
+
+ fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
+ klass_fk = options[:association_foreign_key] || reflection.klass.to_s.foreign_key
+
+ relation = relation.join(join_table, join_type)
+ relation = relation.on(
+ join_table[fk].
+ eq(parent_table[reflection.active_record.primary_key])
+ )
+
+ join_target_table(
+ relation,
+ target_table[reflection.klass.primary_key].
+ eq(join_table[klass_fk])
+ )
+ end
+
+ def join_has_many_to(relation)
+ if reflection.options[:through]
+ join_has_many_through_to(relation)
+ elsif reflection.options[:as]
+ join_has_many_polymorphic_to(relation)
+ else
+ foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
+ primary_key = options[:primary_key] || parent.primary_key
+
+ join_target_table(
+ relation,
+ target_table[foreign_key].
+ eq(parent_table[primary_key])
+ )
+ end
+ end
+ alias :join_has_one_to :join_has_many_to
+
+ def join_has_many_through_to(relation)
+ join_table = Arel::Table.new(
+ through_reflection.klass.table_name, :engine => arel_engine,
+ :as => @aliased_join_table_name
+ )
+
+ jt_conditions = []
+ jt_foreign_key = first_key = second_key = nil
+
+ if through_reflection.options[:as] # has_many :through against a polymorphic join
+ as_key = through_reflection.options[:as].to_s
+ jt_foreign_key = as_key + '_id'
+
+ jt_conditions <<
+ join_table[as_key + '_type'].
+ eq(parent.active_record.base_class.name)
+ else
+ jt_foreign_key = through_reflection.primary_key_name
+ end
+
+ case source_reflection.macro
+ when :has_many
+ second_key = options[:foreign_key] || primary_key
+
+ if source_reflection.options[:as]
+ first_key = "#{source_reflection.options[:as]}_id"
+ else
+ first_key = through_reflection.klass.base_class.to_s.foreign_key
+ end
+
+ unless through_reflection.klass.descends_from_active_record?
+ jt_conditions <<
+ join_table[through_reflection.active_record.inheritance_column].
+ eq(through_reflection.klass.sti_name)
+ end
+ when :belongs_to
+ first_key = primary_key
+
+ if reflection.options[:source_type]
+ second_key = source_reflection.association_foreign_key
+
+ jt_conditions <<
+ join_table[reflection.source_reflection.options[:foreign_type]].
+ eq(reflection.options[:source_type])
+ else
+ second_key = source_reflection.primary_key_name
+ end
+ end
+
+ jt_conditions <<
+ parent_table[parent.primary_key].
+ eq(join_table[jt_foreign_key])
+
+ if through_reflection.options[:conditions]
+ jt_conditions << process_conditions(through_reflection.options[:conditions], aliased_table_name)
+ end
+
+ relation = relation.join(join_table, join_type).on(*jt_conditions)
+
+ join_target_table(
+ relation,
+ target_table[first_key].eq(join_table[second_key])
+ )
+ end
+
+ def join_has_many_polymorphic_to(relation)
+ join_target_table(
+ relation,
+ target_table["#{reflection.options[:as]}_id"].
+ eq(parent_table[parent.primary_key]),
+ target_table["#{reflection.options[:as]}_type"].
+ eq(parent.active_record.base_class.name)
+ )
+ end
+
+ def join_belongs_to_to(relation)
+ foreign_key = options[:foreign_key] || reflection.primary_key_name
+ primary_key = options[:primary_key] || reflection.klass.primary_key
+
+ join_target_table(
+ relation,
+ target_table[primary_key].eq(parent_table[foreign_key])
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb
new file mode 100644
index 0000000000..ed05003f66
--- /dev/null
+++ b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb
@@ -0,0 +1,34 @@
+module ActiveRecord
+ module Associations
+ module ClassMethods
+ class JoinDependency # :nodoc:
+ class JoinBase < JoinPart # :nodoc:
+ # Extra joins provided when the JoinDependency was created
+ attr_reader :table_joins
+
+ def initialize(active_record, joins = nil)
+ super(active_record)
+ @table_joins = joins
+ end
+
+ def ==(other)
+ other.class == self.class &&
+ other.active_record == active_record
+ end
+
+ def aliased_prefix
+ "t0"
+ end
+
+ def table
+ Arel::Table.new(table_name, :engine => arel_engine, :columns => active_record.columns)
+ end
+
+ def aliased_table_name
+ active_record.table_name
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_part.rb
new file mode 100644
index 0000000000..0b093b65e9
--- /dev/null
+++ b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_part.rb
@@ -0,0 +1,80 @@
+module ActiveRecord
+ module Associations
+ module ClassMethods
+ class JoinDependency # :nodoc:
+ # A JoinPart represents a part of a JoinDependency. It is an abstract class, inherited
+ # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
+ # everything else is being joined onto. A JoinAssociation represents an association which
+ # is joining to the base. A JoinAssociation may result in more than one actual join
+ # operations (for example a has_and_belongs_to_many JoinAssociation would result in
+ # two; one for the join table and one for the target table).
+ class JoinPart # :nodoc:
+ # The Active Record class which this join part is associated 'about'; for a JoinBase
+ # this is the actual base model, for a JoinAssociation this is the target model of the
+ # association.
+ attr_reader :active_record
+
+ delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :arel_engine, :to => :active_record
+
+ def initialize(active_record)
+ @active_record = active_record
+ @cached_record = {}
+ @column_names_with_alias = nil
+ end
+
+ def aliased_table
+ Arel::Nodes::TableAlias.new aliased_table_name, table
+ end
+
+ def ==(other)
+ raise NotImplementedError
+ end
+
+ # An Arel::Table for the active_record
+ def table
+ raise NotImplementedError
+ end
+
+ # The prefix to be used when aliasing columns in the active_record's table
+ def aliased_prefix
+ raise NotImplementedError
+ end
+
+ # The alias for the active_record's table
+ def aliased_table_name
+ raise NotImplementedError
+ end
+
+ # The alias for the primary key of the active_record's table
+ def aliased_primary_key
+ "#{aliased_prefix}_r0"
+ end
+
+ # An array of [column_name, alias] pairs for the table
+ def column_names_with_alias
+ unless @column_names_with_alias
+ @column_names_with_alias = []
+
+ ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
+ @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
+ end
+ end
+ @column_names_with_alias
+ end
+
+ def extract_record(row)
+ Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
+ end
+
+ def record_id(row)
+ row[aliased_primary_key]
+ end
+
+ def instantiate(row)
+ @cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index da742fa668..2c72fd0004 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -67,7 +67,7 @@ module ActiveRecord
relation.insert(attributes)
end
- return true
+ true
end
def delete_records(records)
@@ -80,7 +80,7 @@ module ActiveRecord
).delete
end
end
-
+
def construct_joins
"INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 23831e0b08..685d818ab3 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -42,7 +42,7 @@ module ActiveRecord
# documented side-effect of the method that may avoid an extra SELECT.
@target ||= [] and loaded if count == 0
- @reflection.options[:limit] ? [@reflection.options[:limit], count].min : count
+ [@reflection.options[:limit], count].compact.min
end
def has_cached_counter?
@@ -108,7 +108,7 @@ module ActiveRecord
end
def we_can_set_the_inverse_on_this?(record)
- !@reflection.inverse_of.nil?
+ @reflection.inverse_of
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 9e15784f61..9b09b14c87 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1382,30 +1382,6 @@ MSG
result
end
- # Cloned objects have no id assigned and are treated as new records. Note that this is a "shallow" clone
- # as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
- # application specific and is therefore left to the application to implement according to its need.
- def initialize_copy(other)
- _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
- cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
- cloned_attributes.delete(self.class.primary_key)
-
- @attributes = cloned_attributes
-
- @changed_attributes = {}
- attributes_from_column_definition.each do |attr, orig_value|
- @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
- end
-
- clear_aggregation_cache
- clear_association_cache
- @attributes_cache = {}
- @persisted = false
- ensure_proper_type
-
- populate_with_current_scope_attributes
- end
-
# Initialize an empty model object from +coder+. +coder+ must contain
# the attributes necessary for initializing an empty model object. For
# example:
@@ -1615,11 +1591,42 @@ MSG
@attributes.frozen?
end
- # Returns duplicated record with unfreezed attributes.
- def dup
- obj = super
- obj.instance_variable_set('@attributes', @attributes.dup)
- obj
+ # Backport dup from 1.9 so that initialize_dup() gets called
+ unless Object.respond_to?(:initialize_dup)
+ def dup # :nodoc:
+ copy = super
+ copy.initialize_dup(self)
+ copy
+ end
+ end
+
+ # Duped objects have no id assigned and are treated as new records. Note
+ # that this is a "shallow" copy as it copies the object's attributes
+ # only, not its associations. The extent of a "deep" copy is application
+ # specific and is therefore left to the application to implement according
+ # to its need.
+ # The dup method does not preserve the timestamps (created|updated)_(at|on).
+ def initialize_dup(other)
+ cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
+ cloned_attributes.delete(self.class.primary_key)
+
+ @attributes = cloned_attributes
+
+ _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
+
+ @changed_attributes = {}
+ attributes_from_column_definition.each do |attr, orig_value|
+ @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
+ end
+
+ clear_aggregation_cache
+ clear_association_cache
+ @attributes_cache = {}
+ @persisted = false
+
+ ensure_proper_type
+ populate_with_current_scope_attributes
+ clear_timestamp_attributes
end
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
@@ -1826,6 +1833,16 @@ MSG
create_with.each { |att,value| self.respond_to?(:"#{att}=") && self.send("#{att}=", value) } if create_with
end
end
+
+ # Clear attributes and changed_attributes
+ def clear_timestamp_attributes
+ %w(created_at created_on updated_at updated_on).each do |attribute_name|
+ if has_attribute?(attribute_name)
+ self[attribute_name] = nil
+ changed_attributes.delete(attribute_name)
+ end
+ end
+ end
end
Base.class_eval do
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 ca9314ec99..cffa2387de 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,3 +1,4 @@
+require 'thread'
require 'monitor'
require 'set'
require 'active_support/core_ext/module/synchronization'
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 9e1a33a6bf..c0e1dda2bd 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -70,7 +70,7 @@ module ActiveRecord
result[self.class.locking_column] ||= 0
end
- return result
+ result
end
def update(attribute_names = @attributes.keys) #:nodoc:
@@ -89,7 +89,7 @@ module ActiveRecord
affected_rows = relation.where(
relation.table[self.class.primary_key].eq(quoted_id).and(
- relation.table[self.class.locking_column].eq(quote_value(previous_value))
+ relation.table[lock_col].eq(quote_value(previous_value))
)
).arel.update(arel_attributes_values(false, false, attribute_names))
@@ -111,8 +111,9 @@ module ActiveRecord
if persisted?
table = self.class.arel_table
- predicate = table[self.class.primary_key].eq(id)
- predicate = predicate.and(table[self.class.locking_column].eq(send(self.class.locking_column).to_i))
+ lock_col = self.class.locking_column
+ predicate = table[self.class.primary_key].eq(id).
+ and(table[lock_col].eq(send(lock_col).to_i))
affected_rows = self.class.unscoped.where(predicate).delete_all
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 15f83a8579..050b521b6a 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -417,11 +417,8 @@ module ActiveRecord
# Updates a record with the +attributes+ or marks it for destruction if
# +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
- if has_destroy_flag?(attributes) && allow_destroy
- record.mark_for_destruction
- else
- record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
- end
+ record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
+ record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
end
# Determines if a hash contains a truthy _destroy key.
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 4192456447..23ae0b4325 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -202,7 +202,7 @@ module ActiveRecord
end
def construct_relation_for_association_find(join_dependency)
- relation = except(:includes, :eager_load, :preload, :select).select(column_aliases(join_dependency))
+ relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
apply_join_dependency(relation, join_dependency)
end
@@ -349,17 +349,8 @@ module ActiveRecord
end
end
- def column_aliases(join_dependency)
- join_dependency.join_parts.collect { |join_part|
- join_part.column_names_with_alias.collect{ |column_name, aliased_name|
- "#{connection.quote_table_name join_part.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"
- }
- }.flatten.join(", ")
- end
-
def using_limitable_reflections?(reflections)
reflections.none? { |r| r.collection? }
end
-
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 32c7d08daa..70d84619a1 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -26,7 +26,7 @@ module ActiveRecord
when Range, Arel::Relation
attribute.in(value)
when ActiveRecord::Base
- attribute.eq(value.quoted_id)
+ attribute.eq(Arel.sql(value.quoted_id))
when Class
# FIXME: I think we need to deprecate this behavior
attribute.eq(value.name)
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index b13cb2d7a2..fbf7121468 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -163,15 +163,15 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
firm.account = Account.find(:first)
assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! }
- firm = Firm.find(:first).clone
+ firm = Firm.find(:first).dup
firm.account = Account.find(:first)
assert_queries(2) { firm.save! }
- firm = Firm.find(:first).clone
- firm.account = Account.find(:first).clone
+ firm = Firm.find(:first).dup
+ firm.account = Account.find(:first).dup
assert_queries(2) { firm.save! }
end
-
+
def test_callbacks_firing_order_on_create
eye = Eye.create(:iris_attributes => {:color => 'honey'})
assert_equal [true, false], eye.after_create_callbacks_stack
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 26f388ca46..c3ba1f0c35 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -669,64 +669,65 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal true, Topic.find(1).persisted?
end
- def test_clone
+ def test_dup
topic = Topic.find(1)
- cloned_topic = nil
- assert_nothing_raised { cloned_topic = topic.clone }
- assert_equal topic.title, cloned_topic.title
- assert !cloned_topic.persisted?
+ duped_topic = nil
+ assert_nothing_raised { duped_topic = topic.dup }
+ assert_equal topic.title, duped_topic.title
+ assert !duped_topic.persisted?
- # test if the attributes have been cloned
+ # test if the attributes have been duped
topic.title = "a"
- cloned_topic.title = "b"
+ duped_topic.title = "b"
assert_equal "a", topic.title
- assert_equal "b", cloned_topic.title
+ assert_equal "b", duped_topic.title
- # test if the attribute values have been cloned
+ # test if the attribute values have been duped
topic.title = {"a" => "b"}
- cloned_topic = topic.clone
- cloned_topic.title["a"] = "c"
+ duped_topic = topic.dup
+ duped_topic.title["a"] = "c"
assert_equal "b", topic.title["a"]
- # test if attributes set as part of after_initialize are cloned correctly
- assert_equal topic.author_email_address, cloned_topic.author_email_address
+ # test if attributes set as part of after_initialize are duped correctly
+ assert_equal topic.author_email_address, duped_topic.author_email_address
# test if saved clone object differs from original
- cloned_topic.save
- assert cloned_topic.persisted?
- assert_not_equal cloned_topic.id, topic.id
+ duped_topic.save
+ assert duped_topic.persisted?
+ assert_not_equal duped_topic.id, topic.id
- cloned_topic.reload
+ duped_topic.reload
# FIXME: I think this is poor behavior, and will fix it with #5686
- assert_equal({'a' => 'c'}.to_s, cloned_topic.title)
+ assert_equal({'a' => 'c'}.to_s, duped_topic.title)
end
- def test_clone_with_aggregate_of_same_name_as_attribute
+ def test_dup_with_aggregate_of_same_name_as_attribute
dev = DeveloperWithAggregate.find(1)
assert_kind_of DeveloperSalary, dev.salary
- clone = nil
- assert_nothing_raised { clone = dev.clone }
- assert_kind_of DeveloperSalary, clone.salary
- assert_equal dev.salary.amount, clone.salary.amount
- assert !clone.persisted?
+ dup = nil
+ assert_nothing_raised { dup = dev.dup }
+ assert_kind_of DeveloperSalary, dup.salary
+ assert_equal dev.salary.amount, dup.salary.amount
+ assert !dup.persisted?
- # test if the attributes have been cloned
- original_amount = clone.salary.amount
+ # test if the attributes have been dupd
+ original_amount = dup.salary.amount
dev.salary.amount = 1
- assert_equal original_amount, clone.salary.amount
+ assert_equal original_amount, dup.salary.amount
- assert clone.save
- assert clone.persisted?
- assert_not_equal clone.id, dev.id
+ assert dup.save
+ assert dup.persisted?
+ assert_not_equal dup.id, dev.id
end
- def test_clone_does_not_clone_associations
+ def test_dup_does_not_copy_associations
author = authors(:david)
assert_not_equal [], author.posts
+ author.send(:clear_association_cache)
- author_clone = author.clone
- assert_equal [], author_clone.posts
+ author_dup = author.dup
+ assert_equal [], author_dup.posts
end
def test_clone_preserves_subtype
@@ -765,22 +766,22 @@ class BasicsTest < ActiveRecord::TestCase
assert !cloned_developer.salary_changed? # ... and cloned instance should behave same
end
- def test_clone_of_saved_object_marks_attributes_as_dirty
+ def test_dup_of_saved_object_marks_attributes_as_dirty
developer = Developer.create! :name => 'Bjorn', :salary => 100000
assert !developer.name_changed?
assert !developer.salary_changed?
- cloned_developer = developer.clone
+ cloned_developer = developer.dup
assert cloned_developer.name_changed? # both attributes differ from defaults
assert cloned_developer.salary_changed?
end
- def test_clone_of_saved_object_marks_as_dirty_only_changed_attributes
+ def test_dup_of_saved_object_marks_as_dirty_only_changed_attributes
developer = Developer.create! :name => 'Bjorn'
assert !developer.name_changed? # both attributes of saved object should be threated as not changed
assert !developer.salary_changed?
- cloned_developer = developer.clone
+ cloned_developer = developer.dup
assert cloned_developer.name_changed? # ... but on cloned object should be
assert !cloned_developer.salary_changed? # ... BUT salary has non-nil default which should be threated as not changed on cloned instance
end
@@ -1443,10 +1444,6 @@ class BasicsTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = original_logger
end
- def test_dup
- assert !Minimalistic.new.freeze.dup.frozen?
- end
-
def test_compute_type_success
assert_equal Author, ActiveRecord::Base.send(:compute_type, 'Author')
end
diff --git a/activerecord/test/cases/clone_test.rb b/activerecord/test/cases/clone_test.rb
new file mode 100644
index 0000000000..d91646efca
--- /dev/null
+++ b/activerecord/test/cases/clone_test.rb
@@ -0,0 +1,33 @@
+require "cases/helper"
+require 'models/topic'
+
+module ActiveRecord
+ class CloneTest < ActiveRecord::TestCase
+ fixtures :topics
+
+ def test_persisted
+ topic = Topic.first
+ cloned = topic.clone
+ assert topic.persisted?, 'topic persisted'
+ assert cloned.persisted?, 'topic persisted'
+ assert !cloned.new_record?, 'topic is not new'
+ end
+
+ def test_stays_frozen
+ topic = Topic.first
+ topic.freeze
+
+ cloned = topic.clone
+ assert cloned.persisted?, 'topic persisted'
+ assert !cloned.new_record?, 'topic is not new'
+ assert cloned.frozen?, 'topic should be frozen'
+ end
+
+ def test_shallow
+ topic = Topic.first
+ cloned = topic.clone
+ topic.author_name = 'Aaron'
+ assert_equal 'Aaron', cloned.author_name
+ end
+ end
+end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index b1a54af192..a6738fb654 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -338,13 +338,13 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.changed?
end
- def test_cloned_objects_should_not_copy_dirty_flag_from_creator
+ def test_dup_objects_should_not_copy_dirty_flag_from_creator
pirate = Pirate.create!(:catchphrase => "shiver me timbers")
- pirate_clone = pirate.clone
- pirate_clone.reset_catchphrase!
+ pirate_dup = pirate.dup
+ pirate_dup.reset_catchphrase!
pirate.catchphrase = "I love Rum"
assert pirate.catchphrase_changed?
- assert !pirate_clone.catchphrase_changed?
+ assert !pirate_dup.catchphrase_changed?
end
def test_reverted_changes_are_not_dirty
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
new file mode 100644
index 0000000000..0236f9b0a1
--- /dev/null
+++ b/activerecord/test/cases/dup_test.rb
@@ -0,0 +1,103 @@
+require "cases/helper"
+require 'models/topic'
+
+module ActiveRecord
+ class DupTest < ActiveRecord::TestCase
+ fixtures :topics
+
+ def test_dup
+ assert !Topic.new.freeze.dup.frozen?
+ end
+
+ def test_not_readonly
+ topic = Topic.first
+
+ duped = topic.dup
+ assert !duped.readonly?, 'should not be readonly'
+ end
+
+ def test_is_readonly
+ topic = Topic.first
+ topic.readonly!
+
+ duped = topic.dup
+ assert duped.readonly?, 'should be readonly'
+ end
+
+ def test_dup_not_persisted
+ topic = Topic.first
+ duped = topic.dup
+
+ assert !duped.persisted?, 'topic not persisted'
+ assert duped.new_record?, 'topic is new'
+ end
+
+ def test_dup_has_no_id
+ topic = Topic.first
+ duped = topic.dup
+ assert_nil duped.id
+ end
+
+ def test_dup_with_modified_attributes
+ topic = Topic.first
+ topic.author_name = 'Aaron'
+ duped = topic.dup
+ assert_equal 'Aaron', duped.author_name
+ end
+
+ def test_dup_with_changes
+ dbtopic = Topic.first
+ topic = Topic.new
+
+ topic.attributes = dbtopic.attributes
+
+ #duped has no timestamp values
+ duped = dbtopic.dup
+
+ #clear topic timestamp values
+ topic.send(:clear_timestamp_attributes)
+
+ assert_equal topic.changes, duped.changes
+ end
+
+ def test_dup_topics_are_independent
+ topic = Topic.first
+ topic.author_name = 'Aaron'
+ duped = topic.dup
+
+ duped.author_name = 'meow'
+
+ assert_not_equal topic.changes, duped.changes
+ end
+
+ def test_dup_attributes_are_independent
+ topic = Topic.first
+ duped = topic.dup
+
+ duped.author_name = 'meow'
+ topic.author_name = 'Aaron'
+
+ assert_equal 'Aaron', topic.author_name
+ assert_equal 'meow', duped.author_name
+ end
+
+ def test_dup_timestamps_are_cleared
+ topic = Topic.first
+ assert_not_nil topic.updated_at
+ assert_not_nil topic.created_at
+
+ # temporary change to the topic object
+ topic.updated_at -= 3.days
+
+ #dup should not preserve the timestamps if present
+ new_topic = topic.dup
+ assert_nil new_topic.updated_at
+ assert_nil new_topic.created_at
+
+ new_topic.save
+ assert_not_nil new_topic.updated_at
+ assert_not_nil new_topic.created_at
+ end
+
+ end
+end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 31679b2efe..c3da9cdf53 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -16,7 +16,7 @@ class InheritanceTest < ActiveRecord::TestCase
def test_class_with_blank_sti_name
company = Company.find(:first)
- company = company.clone
+ company = company.dup
company.extend(Module.new {
def read_attribute(name)
return ' ' if name == 'type'
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 4ddcdc010b..f9678cb0c5 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -1,3 +1,4 @@
+require 'thread'
require "cases/helper"
require 'models/person'
require 'models/reader'
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 92af53d56f..fb6a239545 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -827,7 +827,7 @@ class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase
fixtures :owners, :pets
def setup
- Owner.accepts_nested_attributes_for :pets
+ Owner.accepts_nested_attributes_for :pets, :allow_destroy => true
@owner = owners(:ashley)
@pet1, @pet2 = pets(:chew), pets(:mochi)
@@ -844,6 +844,19 @@ class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase
@owner.update_attributes(@params)
assert_equal ['Foo', 'Bar'], @owner.pets.map(&:name)
end
+
+ def test_attr_accessor_of_child_should_be_value_provided_during_update_attributes
+ @owner = owners(:ashley)
+ @pet1 = pets(:chew)
+ assert_equal nil, $current_user
+ attributes = {:pets_attributes => { "1"=> { :id => @pet1.id,
+ :name => "Foo2",
+ :current_user => "John",
+ :_destroy=>true }}}
+ @owner.update_attributes(attributes)
+ assert_equal 'John', $after_destroy_callback_output
+ end
+
end
class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 535bcd4396..1682f34a1d 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -14,11 +14,18 @@ require 'models/bird'
require 'models/car'
require 'models/engine'
require 'models/tyre'
+require 'models/minivan'
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
- :tags, :taggings, :cars
+ :tags, :taggings, :cars, :minivans
+
+ def test_do_not_double_quote_string_id
+ van = Minivan.last
+ assert van
+ assert_equal van.id, Minivan.where(:minivan_id => van).to_a.first.minivan_id
+ end
def test_bind_values
relation = Post.scoped
diff --git a/activerecord/test/models/pet.rb b/activerecord/test/models/pet.rb
index a8bf94dd86..570db4c8d5 100644
--- a/activerecord/test/models/pet.rb
+++ b/activerecord/test/models/pet.rb
@@ -1,5 +1,13 @@
class Pet < ActiveRecord::Base
+
+ attr_accessor :current_user
+
set_primary_key :pet_id
belongs_to :owner, :touch => true
has_many :toys
+
+ after_destroy do |record|
+ $after_destroy_callback_output = record.current_user
+ end
+
end
diff --git a/activeresource/Rakefile b/activeresource/Rakefile
index 905241acc0..cf01bc1389 100644..100755
--- a/activeresource/Rakefile
+++ b/activeresource/Rakefile
@@ -1,4 +1,4 @@
-require 'rake'
+#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rake/gempackagetask'
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index d117ca6356..d117ca6356 100644..100755
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb
index 38ce55f26e..ed16c2f71b 100644
--- a/activesupport/lib/active_support/core_ext/module/synchronization.rb
+++ b/activesupport/lib/active_support/core_ext/module/synchronization.rb
@@ -1,3 +1,4 @@
+require 'thread'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/array/extract_options'
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 320f5c1c92..6a344867ee 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -140,11 +140,10 @@ module ActiveSupport
end
def convert_value(value)
- case value
- when Hash
+ if value.class == Hash
self.class.new_from_hash_copying_default(value)
- when Array
- value.dup.replace(value.collect { |e| e.is_a?(Hash) ? self.class.new_from_hash_copying_default(e) : e })
+ elsif value.is_a?(Array)
+ value.dup.replace(value.map { |e| convert_value(e) })
else
value
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 370f26b0d7..74223dd7f2 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -12,6 +12,9 @@ class HashExtTest < Test::Unit::TestCase
class SubclassingArray < Array
end
+ class SubclassingHash < Hash
+ end
+
def setup
@strings = { 'a' => 1, 'b' => 2 }
@symbols = { :a => 1, :b => 2 }
@@ -105,6 +108,11 @@ class HashExtTest < Test::Unit::TestCase
assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
end
+ def test_hash_subclass
+ flash = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of SubclassingHash, flash["foo"]
+ end
+
def test_indifferent_assorted
@strings = @strings.with_indifferent_access
@symbols = @symbols.with_indifferent_access
diff --git a/activesupport/test/core_ext/module/synchronization_test.rb b/activesupport/test/core_ext/module/synchronization_test.rb
index eb850893f0..6c407e2260 100644
--- a/activesupport/test/core_ext/module/synchronization_test.rb
+++ b/activesupport/test/core_ext/module/synchronization_test.rb
@@ -1,3 +1,4 @@
+require 'thread'
require 'abstract_unit'
require 'active_support/core_ext/class/attribute_accessors'
diff --git a/railties/Rakefile b/railties/Rakefile
index 9504661c0e..5137bee761 100644..100755
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -1,4 +1,4 @@
-require 'rake'
+#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/gempackagetask'
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile
index d83cafc3be..4dc1023f1f 100644..100755
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile
@@ -1,7 +1,7 @@
+#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require File.expand_path('../config/application', __FILE__)
-require 'rake'
<%= app_const %>.load_tasks
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt b/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt
index 733e321c94..77149ae351 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt
@@ -1,4 +1,4 @@
-require 'rake'
+#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/rdoctask'
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
index 88f50f9f04..12350309bf 100644..100755
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -1,12 +1,10 @@
-# encoding: UTF-8
-require 'rubygems'
+#!/usr/bin/env rake
begin
require 'bundler/setup'
rescue LoadError
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
end
-require 'rake'
require 'rake/rdoctask'
Rake::RDocTask.new(:rdoc) do |rdoc|