mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Merge branch 'master' into nested_has_many_through
Conflicts: activerecord/lib/active_record/associations/has_many_association.rb activerecord/lib/active_record/associations/through_association_scope.rb
This commit is contained in:
4
Gemfile
4
Gemfile
@@ -31,10 +31,6 @@ platforms :mri_18 do
|
||||
gem 'ruby-prof'
|
||||
end
|
||||
|
||||
platforms :mri_19 do
|
||||
gem "ruby-debug19"
|
||||
end
|
||||
|
||||
platforms :ruby do
|
||||
gem 'json'
|
||||
gem 'yajl-ruby'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
require 'active_support/inflector'
|
||||
require 'active_support/core_ext/hash/except'
|
||||
require 'active_support/core_ext/module/introspection'
|
||||
|
||||
module ActiveModel
|
||||
class Name < String
|
||||
|
||||
@@ -338,7 +338,7 @@ module ActiveRecord
|
||||
klass = klass_name.constantize
|
||||
|
||||
table_name = klass.quoted_table_name
|
||||
primary_key = reflection.options[:primary_key] || klass.primary_key
|
||||
primary_key = (reflection.options[:primary_key] || klass.primary_key).to_s
|
||||
column_type = klass.columns.detect{|c| c.name == primary_key}.type
|
||||
|
||||
ids = _id_map.keys.map do |id|
|
||||
|
||||
@@ -1880,7 +1880,7 @@ module ActiveRecord
|
||||
|
||||
def initialize(base, associations, joins)
|
||||
@join_parts = [JoinBase.new(base, joins)]
|
||||
@associations = associations
|
||||
@associations = {}
|
||||
@reflections = []
|
||||
@base_records_hash = {}
|
||||
@base_records_in_order = []
|
||||
@@ -1949,30 +1949,58 @@ module ActiveRecord
|
||||
|
||||
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?"
|
||||
@reflections << reflection
|
||||
join_association = build_join_association(reflection, parent)
|
||||
join_association.join_type = join_type
|
||||
@join_parts << join_association
|
||||
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|
|
||||
build(name, parent, join_type)
|
||||
build(associations[name], nil, join_type)
|
||||
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! }
|
||||
@@ -2045,7 +2073,7 @@ module ActiveRecord
|
||||
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
|
||||
# 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
|
||||
@@ -2124,8 +2152,7 @@ module ActiveRecord
|
||||
|
||||
def ==(other)
|
||||
other.class == self.class &&
|
||||
other.active_record == active_record &&
|
||||
other.table_joins == table_joins
|
||||
other.active_record == active_record
|
||||
end
|
||||
|
||||
def aliased_prefix
|
||||
@@ -2146,7 +2173,7 @@ module ActiveRecord
|
||||
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
|
||||
# relevant for generating aliases which do not conflict with other joins which are
|
||||
# part of the query.
|
||||
attr_reader :join_dependency
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ module ActiveRecord
|
||||
IndexDefinition.new(
|
||||
table_name,
|
||||
row['name'],
|
||||
row['unique'].to_i != 0,
|
||||
row['unique'] != 0,
|
||||
exec("PRAGMA index_info('#{row['name']}')").map { |col|
|
||||
col['name']
|
||||
})
|
||||
@@ -235,7 +235,7 @@ module ActiveRecord
|
||||
|
||||
def primary_key(table_name) #:nodoc:
|
||||
column = table_structure(table_name).find { |field|
|
||||
field['pk'].to_i == 1
|
||||
field['pk'] == 1
|
||||
}
|
||||
column && column['name']
|
||||
end
|
||||
@@ -314,13 +314,12 @@ module ActiveRecord
|
||||
|
||||
protected
|
||||
def select(sql, name = nil, binds = []) #:nodoc:
|
||||
exec(sql, name, binds).map do |row|
|
||||
record = {}
|
||||
row.each do |key, value|
|
||||
record[key.sub(/^"?\w+"?\./, '')] = value if key.is_a?(String)
|
||||
end
|
||||
record
|
||||
end
|
||||
result = exec(sql, name, binds)
|
||||
columns = result.columns.map { |column|
|
||||
column.sub(/^"?\w+"?\./, '')
|
||||
}
|
||||
|
||||
result.rows.map { |row| Hash[columns.zip(row)] }
|
||||
end
|
||||
|
||||
def table_structure(table_name)
|
||||
|
||||
@@ -319,8 +319,13 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def where_values_hash
|
||||
Hash[@where_values.find_all {|w| w.respond_to?(:operator) && w.operator == :== }.map {|where|
|
||||
[where.operand1.name, where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2]
|
||||
Hash[@where_values.find_all { |w|
|
||||
w.respond_to?(:operator) && w.operator == :== && w.left.relation.name == table_name
|
||||
}.map { |where|
|
||||
[
|
||||
where.left.name,
|
||||
where.right.respond_to?(:value) ? where.right.value : where.right
|
||||
]
|
||||
}]
|
||||
end
|
||||
|
||||
|
||||
@@ -28,17 +28,20 @@ module ActiveRecord
|
||||
|
||||
merged_wheres = @where_values + r.where_values
|
||||
|
||||
# Remove duplicates, last one wins.
|
||||
seen = {}
|
||||
merged_wheres = merged_wheres.reverse.reject { |w|
|
||||
nuke = false
|
||||
if w.respond_to?(:operator) && w.operator == :==
|
||||
name = w.left.name
|
||||
nuke = seen[name]
|
||||
seen[name] = true
|
||||
end
|
||||
nuke
|
||||
}.reverse
|
||||
unless @where_values.empty?
|
||||
# Remove duplicates, last one wins.
|
||||
seen = Hash.new { |h,table| h[table] = {} }
|
||||
merged_wheres = merged_wheres.reverse.reject { |w|
|
||||
nuke = false
|
||||
if w.respond_to?(:operator) && w.operator == :==
|
||||
name = w.left.name
|
||||
table = w.left.relation.name
|
||||
nuke = seen[table][name]
|
||||
seen[table][name] = true
|
||||
end
|
||||
nuke
|
||||
}.reverse
|
||||
end
|
||||
|
||||
merged_relation.where_values = merged_wheres
|
||||
|
||||
|
||||
@@ -81,6 +81,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
||||
assert_not_nil citibank_result.instance_variable_get("@firm_with_primary_key")
|
||||
end
|
||||
|
||||
def test_eager_loading_with_primary_key_as_symbol
|
||||
Firm.create("name" => "Apple")
|
||||
Client.create("name" => "Citibank", :firm_name => "Apple")
|
||||
citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key_symbols)
|
||||
assert_not_nil citibank_result.instance_variable_get("@firm_with_primary_key_symbols")
|
||||
end
|
||||
|
||||
def test_no_unexpected_aliasing
|
||||
first_firm = companies(:first_firm)
|
||||
another_firm = companies(:another_firm)
|
||||
|
||||
@@ -3,6 +3,7 @@ require 'models/post'
|
||||
require 'models/comment'
|
||||
require 'models/author'
|
||||
require 'models/categorization'
|
||||
require 'models/category'
|
||||
require 'models/company'
|
||||
require 'models/topic'
|
||||
require 'models/reply'
|
||||
@@ -45,6 +46,31 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
|
||||
assert_equal people(:michael), Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').first
|
||||
end
|
||||
|
||||
def test_cascaded_eager_association_loading_with_join_for_count
|
||||
categories = Category.joins(:categorizations).includes([{:posts=>:comments}, :authors])
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_equal 2, categories.count
|
||||
assert_equal 2, categories.all.uniq.size # Must uniq since instantiating with inner joins will get dupes
|
||||
end
|
||||
end
|
||||
|
||||
def test_cascaded_eager_association_loading_with_duplicated_includes
|
||||
categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null")
|
||||
assert_nothing_raised do
|
||||
assert_equal 2, categories.count
|
||||
assert_equal 2, categories.all.size
|
||||
end
|
||||
end
|
||||
|
||||
def test_cascaded_eager_association_loading_with_twice_includes_edge_cases
|
||||
categories = Category.includes(:categorizations => :author).includes(:categorizations => :post).where("posts.id is not null")
|
||||
assert_nothing_raised do
|
||||
assert_equal 2, categories.count
|
||||
assert_equal 2, categories.all.size
|
||||
end
|
||||
end
|
||||
|
||||
def test_eager_association_loading_with_join_for_count
|
||||
authors = Author.joins(:special_posts).includes([:posts, :categorizations])
|
||||
|
||||
|
||||
@@ -226,6 +226,12 @@ class MethodScopingTest < ActiveRecord::TestCase
|
||||
assert Post.find(1).comments.include?(new_comment)
|
||||
end
|
||||
|
||||
def test_scoped_create_with_join_and_merge
|
||||
(Comment.where(:body => "but Who's Buying?").joins(:post) & Post.where(:body => 'Peace Sells...')).with_scope do
|
||||
assert_equal({:body => "but Who's Buying?"}, Comment.scoped.scope_for_create)
|
||||
end
|
||||
end
|
||||
|
||||
def test_immutable_scope
|
||||
options = { :conditions => "name = 'David'" }
|
||||
Developer.send(:with_scope, :find => options) do
|
||||
|
||||
@@ -500,6 +500,11 @@ class RelationTest < ActiveRecord::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_relation_merging_with_joins
|
||||
comments = Comment.joins(:post).where(:body => 'Thank you for the welcome') & Post.where(:body => 'Such a lovely day')
|
||||
assert_equal 1, comments.count
|
||||
end
|
||||
|
||||
def test_count
|
||||
posts = Post.scoped
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ class Client < Company
|
||||
belongs_to :firm_with_other_name, :class_name => "Firm", :foreign_key => "client_of"
|
||||
belongs_to :firm_with_condition, :class_name => "Firm", :foreign_key => "client_of", :conditions => ["1 = ?", 1]
|
||||
belongs_to :firm_with_primary_key, :class_name => "Firm", :primary_key => "name", :foreign_key => "firm_name"
|
||||
belongs_to :firm_with_primary_key_symbols, :class_name => "Firm", :primary_key => :name, :foreign_key => :firm_name
|
||||
belongs_to :readonly_firm, :class_name => "Firm", :foreign_key => "firm_id", :readonly => true
|
||||
|
||||
# Record destruction so we can test whether firm.clients.clear has
|
||||
|
||||
@@ -233,7 +233,7 @@ h4. AssetTagHelper
|
||||
|
||||
This module provides methods for generating HTML that links views to assets such as images, javascripts, stylesheets, and feeds.
|
||||
|
||||
By default, Rails links to these assets on the current host in the public folder, but you can direct Rails to link to assets from a dedicated assets server by setting +ActionController::Base.asset_host+ in your +config/environment.rb+. For example, let's say your asset host is +assets.example.com+:
|
||||
By default, Rails links to these assets on the current host in the public folder, but you can direct Rails to link to assets from a dedicated assets server by setting +ActionController::Base.asset_host+ in the application configuration, typically in +config/environments/production.rb+. For example, let's say your asset host is +assets.example.com+:
|
||||
|
||||
<ruby>
|
||||
ActionController::Base.asset_host = "assets.example.com"
|
||||
|
||||
@@ -1128,14 +1128,14 @@ As with callback classes, the observer's methods receive the observed model as a
|
||||
|
||||
h4. Registering Observers
|
||||
|
||||
Observers are conventionally placed inside of your +app/models+ directory and registered in your application's +config/environment.rb+ file. For example, the +UserObserver+ above would be saved as +app/models/user_observer.rb+ and registered in +config/environment.rb+ this way:
|
||||
Observers are conventionally placed inside of your +app/models+ directory and registered in your application's +config/application.rb+ file. For example, the +UserObserver+ above would be saved as +app/models/user_observer.rb+ and registered in +config/application.rb+ this way:
|
||||
|
||||
<ruby>
|
||||
# Activate observers that should always be running
|
||||
config.active_record.observers = :user_observer
|
||||
</ruby>
|
||||
|
||||
As usual, settings in +config/environments+ take precedence over those in +config/environment.rb+. So, if you prefer that an observer doesn't run in all environments, you can simply register it in a specific environment instead.
|
||||
As usual, settings in +config/environments+ take precedence over those in +config/application.rb+. So, if you prefer that an observer doesn't run in all environments, you can simply register it in a specific environment instead.
|
||||
|
||||
h4. Sharing Observers
|
||||
|
||||
@@ -1151,7 +1151,7 @@ class MailerObserver < ActiveRecord::Observer
|
||||
end
|
||||
</ruby>
|
||||
|
||||
In this example, the +after_create+ method would be called whenever a +Registration+ or +User+ was created. Note that this new +MailerObserver+ would also need to be registered in +config/environment.rb+ in order to take effect.
|
||||
In this example, the +after_create+ method would be called whenever a +Registration+ or +User+ was created. Note that this new +MailerObserver+ would also need to be registered in +config/application.rb+ in order to take effect.
|
||||
|
||||
<ruby>
|
||||
# Activate observers that should always be running
|
||||
|
||||
@@ -225,6 +225,8 @@ The debugger used by Rails, +ruby-debug+, comes as a gem. To install it, just ru
|
||||
$ sudo gem install ruby-debug
|
||||
</shell>
|
||||
|
||||
TIP: If you are using Ruby 1.9, you can install a compatible version of +ruby-debug+ by running +sudo gem install ruby-debug19+
|
||||
|
||||
In case you want to download a particular version or get the source code, refer to the "project's page on rubyforge":http://rubyforge.org/projects/ruby-debug/.
|
||||
|
||||
Rails has had built-in support for ruby-debug since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the +debugger+ method.
|
||||
|
||||
@@ -546,7 +546,7 @@ Schema files are also useful if you want a quick look at what attributes an Acti
|
||||
|
||||
h4. Types of Schema Dumps
|
||||
|
||||
There are two ways to dump the schema. This is set in +config/environment.rb+ by the +config.active_record.schema_format+ setting, which may be either +:sql+ or +:ruby+.
|
||||
There are two ways to dump the schema. This is set in +config/application.rb+ by the +config.active_record.schema_format+ setting, which may be either +:sql+ or +:ruby+.
|
||||
|
||||
If +:ruby+ is selected then the schema is stored in +db/schema.rb+. If you look at this file you'll find that it looks an awful lot like one very big migration:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user