activerecord 1.13.2 → 1.14.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +452 -10
- data/RUNNING_UNIT_TESTS +1 -1
- data/lib/active_record.rb +5 -2
- data/lib/active_record/acts/list.rb +1 -1
- data/lib/active_record/acts/tree.rb +29 -25
- data/lib/active_record/aggregations.rb +3 -2
- data/lib/active_record/associations.rb +783 -337
- data/lib/active_record/associations/association_collection.rb +7 -12
- data/lib/active_record/associations/association_proxy.rb +62 -24
- data/lib/active_record/associations/belongs_to_association.rb +27 -46
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +38 -38
- data/lib/active_record/associations/has_many_association.rb +61 -56
- data/lib/active_record/associations/has_many_through_association.rb +144 -0
- data/lib/active_record/associations/has_one_association.rb +22 -16
- data/lib/active_record/base.rb +482 -182
- data/lib/active_record/calculations.rb +225 -0
- data/lib/active_record/callbacks.rb +7 -7
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +162 -47
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +21 -1
- data/lib/active_record/connection_adapters/abstract_adapter.rb +34 -2
- data/lib/active_record/connection_adapters/db2_adapter.rb +107 -61
- data/lib/active_record/connection_adapters/mysql_adapter.rb +29 -6
- data/lib/active_record/connection_adapters/openbase_adapter.rb +349 -0
- data/lib/active_record/connection_adapters/{oci_adapter.rb → oracle_adapter.rb} +125 -59
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +24 -21
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +47 -8
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +36 -16
- data/lib/active_record/connection_adapters/sybase_adapter.rb +684 -0
- data/lib/active_record/fixtures.rb +42 -17
- data/lib/active_record/locking.rb +36 -15
- data/lib/active_record/migration.rb +111 -8
- data/lib/active_record/observer.rb +25 -1
- data/lib/active_record/reflection.rb +103 -41
- data/lib/active_record/schema.rb +2 -2
- data/lib/active_record/schema_dumper.rb +55 -18
- data/lib/active_record/timestamp.rb +6 -6
- data/lib/active_record/validations.rb +65 -40
- data/lib/active_record/vendor/db2.rb +10 -5
- data/lib/active_record/vendor/simple.rb +693 -702
- data/lib/active_record/version.rb +2 -2
- data/rakefile +4 -4
- data/test/aaa_create_tables_test.rb +25 -6
- data/test/abstract_unit.rb +39 -1
- data/test/adapter_test.rb +31 -4
- data/test/associations_cascaded_eager_loading_test.rb +106 -0
- data/test/associations_go_eager_test.rb +85 -16
- data/test/associations_join_model_test.rb +338 -0
- data/test/associations_test.rb +129 -50
- data/test/base_test.rb +204 -49
- data/test/binary_test.rb +1 -1
- data/test/calculations_test.rb +169 -0
- data/test/callbacks_test.rb +5 -23
- data/test/class_inheritable_attributes_test.rb +1 -1
- data/test/column_alias_test.rb +1 -1
- data/test/connections/native_mysql/connection.rb +1 -0
- data/test/connections/native_openbase/connection.rb +22 -0
- data/test/connections/{native_oci → native_oracle}/connection.rb +7 -9
- data/test/connections/native_sqlite/connection.rb +1 -1
- data/test/connections/native_sqlite3/connection.rb +1 -0
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -0
- data/test/connections/native_sybase/connection.rb +24 -0
- data/test/defaults_test.rb +18 -0
- data/test/deprecated_associations_test.rb +2 -2
- data/test/deprecated_finder_test.rb +0 -6
- data/test/finder_test.rb +26 -23
- data/test/fixtures/accounts.yml +10 -0
- data/test/fixtures/author.rb +31 -6
- data/test/fixtures/author_favorites.yml +4 -0
- data/test/fixtures/categories/special_categories.yml +9 -0
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
- data/test/fixtures/categories_posts.yml +4 -0
- data/test/fixtures/categorization.rb +5 -0
- data/test/fixtures/categorizations.yml +11 -0
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +17 -5
- data/test/fixtures/company_in_module.rb +19 -5
- data/test/fixtures/db_definitions/db2.drop.sql +3 -0
- data/test/fixtures/db_definitions/db2.sql +121 -100
- data/test/fixtures/db_definitions/db22.sql +2 -2
- data/test/fixtures/db_definitions/firebird.drop.sql +4 -0
- data/test/fixtures/db_definitions/firebird.sql +26 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +3 -0
- data/test/fixtures/db_definitions/mysql.sql +21 -1
- data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase.sql +282 -0
- data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase2.sql +7 -0
- data/test/fixtures/db_definitions/{oci.drop.sql → oracle.drop.sql} +6 -0
- data/test/fixtures/db_definitions/{oci.sql → oracle.sql} +25 -4
- data/test/fixtures/db_definitions/{oci2.drop.sql → oracle2.drop.sql} +0 -0
- data/test/fixtures/db_definitions/{oci2.sql → oracle2.sql} +0 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
- data/test/fixtures/db_definitions/postgresql.sql +22 -1
- data/test/fixtures/db_definitions/schema.rb +32 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlite.sql +18 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +23 -3
- data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
- data/test/fixtures/db_definitions/sybase.sql +204 -0
- data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
- data/test/fixtures/db_definitions/sybase2.sql +5 -0
- data/test/fixtures/developers.yml +6 -1
- data/test/fixtures/developers_projects.yml +4 -0
- data/test/fixtures/funny_jokes.yml +14 -0
- data/test/fixtures/joke.rb +6 -0
- data/test/fixtures/legacy_thing.rb +3 -0
- data/test/fixtures/legacy_things.yml +3 -0
- data/test/fixtures/mixin.rb +1 -1
- data/test/fixtures/person.rb +4 -1
- data/test/fixtures/post.rb +26 -1
- data/test/fixtures/project.rb +1 -0
- data/test/fixtures/reader.rb +4 -0
- data/test/fixtures/readers.yml +4 -0
- data/test/fixtures/reply.rb +2 -1
- data/test/fixtures/tag.rb +5 -0
- data/test/fixtures/tagging.rb +6 -0
- data/test/fixtures/taggings.yml +18 -0
- data/test/fixtures/tags.yml +7 -0
- data/test/fixtures/tasks.yml +2 -2
- data/test/fixtures/topic.rb +2 -2
- data/test/fixtures/topics.yml +1 -0
- data/test/fixtures_test.rb +47 -13
- data/test/inheritance_test.rb +2 -2
- data/test/locking_test.rb +15 -1
- data/test/method_scoping_test.rb +248 -13
- data/test/migration_test.rb +68 -11
- data/test/mixin_nested_set_test.rb +1 -1
- data/test/modules_test.rb +6 -1
- data/test/readonly_test.rb +1 -1
- data/test/reflection_test.rb +63 -9
- data/test/schema_dumper_test.rb +41 -0
- data/test/{synonym_test_oci.rb → synonym_test_oracle.rb} +1 -1
- data/test/threaded_connections_test.rb +10 -0
- data/test/unconnected_test.rb +12 -5
- data/test/validations_test.rb +197 -10
- metadata +295 -260
- data/test/fixtures/db_definitions/create_oracle_db.bat +0 -0
- data/test/fixtures/db_definitions/create_oracle_db.sh +0 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
data/RUNNING_UNIT_TESTS
CHANGED
@@ -15,7 +15,7 @@ connection.rb otherwise (on Postgres, at least) tests for default values will fa
|
|
15
15
|
The easiest way to run the unit tests is through Rake. The default task runs
|
16
16
|
the entire test suite for all the adapters. You can also run the suite on just
|
17
17
|
one adapter by using the tasks test_mysql_ruby, test_ruby_mysql, test_sqlite,
|
18
|
-
or
|
18
|
+
or test_postgresql. For more information, checkout the full array of rake tasks with "rake -T"
|
19
19
|
|
20
20
|
Rake can be found at http://rake.rubyforge.org
|
21
21
|
|
data/lib/active_record.rb
CHANGED
@@ -38,10 +38,10 @@ require 'active_record/base'
|
|
38
38
|
require 'active_record/observer'
|
39
39
|
require 'active_record/validations'
|
40
40
|
require 'active_record/callbacks'
|
41
|
+
require 'active_record/reflection'
|
41
42
|
require 'active_record/associations'
|
42
43
|
require 'active_record/aggregations'
|
43
44
|
require 'active_record/transactions'
|
44
|
-
require 'active_record/reflection'
|
45
45
|
require 'active_record/timestamp'
|
46
46
|
require 'active_record/acts/list'
|
47
47
|
require 'active_record/acts/tree'
|
@@ -49,6 +49,7 @@ require 'active_record/acts/nested_set'
|
|
49
49
|
require 'active_record/locking'
|
50
50
|
require 'active_record/migration'
|
51
51
|
require 'active_record/schema'
|
52
|
+
require 'active_record/calculations'
|
52
53
|
|
53
54
|
ActiveRecord::Base.class_eval do
|
54
55
|
include ActiveRecord::Validations
|
@@ -63,10 +64,11 @@ ActiveRecord::Base.class_eval do
|
|
63
64
|
include ActiveRecord::Acts::Tree
|
64
65
|
include ActiveRecord::Acts::List
|
65
66
|
include ActiveRecord::Acts::NestedSet
|
67
|
+
include ActiveRecord::Calculations
|
66
68
|
end
|
67
69
|
|
68
70
|
unless defined?(RAILS_CONNECTION_ADAPTERS)
|
69
|
-
RAILS_CONNECTION_ADAPTERS = %w(mysql postgresql sqlite firebird sqlserver db2
|
71
|
+
RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase )
|
70
72
|
end
|
71
73
|
|
72
74
|
RAILS_CONNECTION_ADAPTERS.each do |adapter|
|
@@ -74,3 +76,4 @@ RAILS_CONNECTION_ADAPTERS.each do |adapter|
|
|
74
76
|
end
|
75
77
|
|
76
78
|
require 'active_record/query_cache'
|
79
|
+
require 'active_record/schema_dumper'
|
@@ -174,7 +174,7 @@ module ActiveRecord
|
|
174
174
|
|
175
175
|
def bottom_item(except = nil)
|
176
176
|
conditions = scope_condition
|
177
|
-
conditions = "#{conditions} AND
|
177
|
+
conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
|
178
178
|
acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
|
179
179
|
end
|
180
180
|
|
@@ -45,40 +45,44 @@ module ActiveRecord
|
|
45
45
|
configuration.update(options) if options.is_a?(Hash)
|
46
46
|
|
47
47
|
belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
|
48
|
-
has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent =>
|
48
|
+
has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy
|
49
49
|
|
50
|
-
|
50
|
+
class_eval <<-EOV
|
51
|
+
include ActiveRecord::Acts::Tree::InstanceMethods
|
52
|
+
|
51
53
|
def self.roots
|
52
|
-
|
54
|
+
find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
|
53
55
|
end
|
56
|
+
|
54
57
|
def self.root
|
55
|
-
|
58
|
+
find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
|
56
59
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
#
|
61
|
-
# subchild1.ancestors # => [child1, root]
|
62
|
-
define_method(:ancestors) do
|
63
|
-
node, nodes = self, []
|
64
|
-
nodes << node = node.parent until not node.has_parent?
|
65
|
-
nodes
|
66
|
-
end
|
60
|
+
EOV
|
61
|
+
end
|
62
|
+
end
|
67
63
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
64
|
+
module InstanceMethods
|
65
|
+
# Returns list of ancestors, starting from parent until root.
|
66
|
+
#
|
67
|
+
# subchild1.ancestors # => [child1, root]
|
68
|
+
def ancestors
|
69
|
+
node, nodes = self, []
|
70
|
+
nodes << node = node.parent until not node.has_parent?
|
71
|
+
nodes
|
72
|
+
end
|
73
73
|
|
74
|
-
|
75
|
-
|
76
|
-
|
74
|
+
def root
|
75
|
+
node = self
|
76
|
+
node = node.parent until not node.has_parent?
|
77
|
+
node
|
78
|
+
end
|
77
79
|
|
78
|
-
|
79
|
-
|
80
|
-
|
80
|
+
def siblings
|
81
|
+
self_and_siblings - [self]
|
82
|
+
end
|
81
83
|
|
84
|
+
def self_and_siblings
|
85
|
+
has_parent? ? parent.children : self.class.roots
|
82
86
|
end
|
83
87
|
end
|
84
88
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Aggregations # :nodoc:
|
3
|
-
def self.
|
4
|
-
super
|
3
|
+
def self.included(base)
|
5
4
|
base.extend(ClassMethods)
|
6
5
|
end
|
7
6
|
|
@@ -134,6 +133,8 @@ module ActiveRecord
|
|
134
133
|
|
135
134
|
reader_method(name, class_name, mapping)
|
136
135
|
writer_method(name, class_name, mapping)
|
136
|
+
|
137
|
+
create_reflection(:composed_of, part_id, options, self)
|
137
138
|
end
|
138
139
|
|
139
140
|
private
|
@@ -1,12 +1,59 @@
|
|
1
1
|
require 'active_record/associations/association_proxy'
|
2
2
|
require 'active_record/associations/association_collection'
|
3
3
|
require 'active_record/associations/belongs_to_association'
|
4
|
+
require 'active_record/associations/belongs_to_polymorphic_association'
|
4
5
|
require 'active_record/associations/has_one_association'
|
5
6
|
require 'active_record/associations/has_many_association'
|
7
|
+
require 'active_record/associations/has_many_through_association'
|
6
8
|
require 'active_record/associations/has_and_belongs_to_many_association'
|
7
9
|
require 'active_record/deprecated_associations'
|
8
10
|
|
9
11
|
module ActiveRecord
|
12
|
+
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
|
13
|
+
def initialize(reflection)
|
14
|
+
@reflection = reflection
|
15
|
+
end
|
16
|
+
|
17
|
+
def message
|
18
|
+
"Could not find the association #{@reflection.options[:through].inspect} in model #{@reflection.klass}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
|
23
|
+
def initialize(owner_class_name, reflection, source_reflection)
|
24
|
+
@owner_class_name = owner_class_name
|
25
|
+
@reflection = reflection
|
26
|
+
@source_reflection = source_reflection
|
27
|
+
end
|
28
|
+
|
29
|
+
def message
|
30
|
+
"Cannot have a has_many :through association '#{@owner_class_name}##{@reflection.name}' on the polymorphic object '#{@source_reflection.class_name}##{@source_reflection.name}'."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
|
35
|
+
def initialize(reflection)
|
36
|
+
@reflection = reflection
|
37
|
+
@through_reflection = reflection.through_reflection
|
38
|
+
@source_reflection_names = reflection.source_reflection_names
|
39
|
+
@source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
|
40
|
+
end
|
41
|
+
|
42
|
+
def message
|
43
|
+
"Could not find the source association(s) #{@source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{@through_reflection.klass}. Try 'has_many #{@reflection.name.inspect}, :through => #{@through_reflection.name.inspect}, :source => <name>'. Is it one of #{@source_associations.to_sentence :connector => 'or'}?"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
|
48
|
+
def initialize(reflection)
|
49
|
+
@reflection = reflection
|
50
|
+
end
|
51
|
+
|
52
|
+
def message
|
53
|
+
"Can not eagerly load the polymorphic association #{@reflection.name.inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
10
57
|
module Associations # :nodoc:
|
11
58
|
def self.append_features(base)
|
12
59
|
super
|
@@ -123,15 +170,13 @@ module ActiveRecord
|
|
123
170
|
# === Association extensions
|
124
171
|
#
|
125
172
|
# The proxy objects that controls the access to associations can be extended through anonymous modules. This is especially
|
126
|
-
# beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this
|
173
|
+
# beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
|
127
174
|
# Example:
|
128
175
|
#
|
129
176
|
# class Account < ActiveRecord::Base
|
130
177
|
# has_many :people do
|
131
178
|
# def find_or_create_by_name(name)
|
132
|
-
# first_name,
|
133
|
-
# last_name = last_name.join " "
|
134
|
-
#
|
179
|
+
# first_name, last_name = name.split(" ", 2)
|
135
180
|
# find_or_create_by_first_name_and_last_name(first_name, last_name)
|
136
181
|
# end
|
137
182
|
# end
|
@@ -145,9 +190,7 @@ module ActiveRecord
|
|
145
190
|
#
|
146
191
|
# module FindOrCreateByNameExtension
|
147
192
|
# def find_or_create_by_name(name)
|
148
|
-
# first_name,
|
149
|
-
# last_name = last_name.join " "
|
150
|
-
#
|
193
|
+
# first_name, last_name = name.split(" ", 2)
|
151
194
|
# find_or_create_by_first_name_and_last_name(first_name, last_name)
|
152
195
|
# end
|
153
196
|
# end
|
@@ -160,6 +203,64 @@ module ActiveRecord
|
|
160
203
|
# has_many :people, :extend => FindOrCreateByNameExtension
|
161
204
|
# end
|
162
205
|
#
|
206
|
+
# === Association Join Models
|
207
|
+
#
|
208
|
+
# Has Many associations can be configured with the :through option to use an explicit join model to retrieve the data. This
|
209
|
+
# operates similarly to a <tt>has_and_belongs_to_many</tt> association. The advantage is that you're able to add validations,
|
210
|
+
# callbacks, and extra attributes on the join model. Consider the following schema:
|
211
|
+
#
|
212
|
+
# class Author < ActiveRecord::Base
|
213
|
+
# has_many :authorships
|
214
|
+
# has_many :books, :through => :authorships
|
215
|
+
# end
|
216
|
+
#
|
217
|
+
# class Authorship < ActiveRecord::Base
|
218
|
+
# belongs_to :author
|
219
|
+
# belongs_to :book
|
220
|
+
# end
|
221
|
+
#
|
222
|
+
# @author = Author.find :first
|
223
|
+
# @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to.
|
224
|
+
# @author.books # selects all books by using the Authorship join model
|
225
|
+
#
|
226
|
+
# You can also go through a has_many association on the join model:
|
227
|
+
#
|
228
|
+
# class Firm < ActiveRecord::Base
|
229
|
+
# has_many :clients
|
230
|
+
# has_many :invoices, :through => :clients
|
231
|
+
# end
|
232
|
+
#
|
233
|
+
# class Client < ActiveRecord::Base
|
234
|
+
# belongs_to :firm
|
235
|
+
# has_many :invoices
|
236
|
+
# end
|
237
|
+
#
|
238
|
+
# class Invoice < ActiveRecord::Base
|
239
|
+
# belongs_to :client
|
240
|
+
# end
|
241
|
+
#
|
242
|
+
# @firm = Firm.find :first
|
243
|
+
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
|
244
|
+
# @firm.invoices # selects all invoices by going through the Client join model.
|
245
|
+
#
|
246
|
+
# === Polymorphic Associations
|
247
|
+
#
|
248
|
+
# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
|
249
|
+
# specify an interface that a has_many association must adhere to.
|
250
|
+
#
|
251
|
+
# class Asset < ActiveRecord::Base
|
252
|
+
# belongs_to :attachable, :polymorphic => true
|
253
|
+
# end
|
254
|
+
#
|
255
|
+
# class Post < ActiveRecord::Base
|
256
|
+
# has_many :assets, :as => :attachable # The <tt>:as</tt> option specifies the polymorphic interface to use.
|
257
|
+
# end
|
258
|
+
#
|
259
|
+
# @asset.attachable = @post
|
260
|
+
#
|
261
|
+
# This works by using a type column in addition to a foreign key to specify the associated record. In the Asset example, you'd need
|
262
|
+
# an attachable_id integer column and an attachable_type string column.
|
263
|
+
#
|
163
264
|
# == Caching
|
164
265
|
#
|
165
266
|
# All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
|
@@ -220,9 +321,57 @@ module ActiveRecord
|
|
220
321
|
# in both conditions and orders. So :order => "posts.id DESC" will work while :order => "id DESC" will not. This may require that
|
221
322
|
# you alter the :order and :conditions on the association definitions themselves.
|
222
323
|
#
|
223
|
-
# It's currently not possible to use eager loading on multiple associations from the same table. Eager loading will
|
324
|
+
# It's currently not possible to use eager loading on multiple associations from the same table. Eager loading will not pull
|
224
325
|
# additional attributes on join tables, so "rich associations" with has_and_belongs_to_many is not a good fit for eager loading.
|
326
|
+
#
|
327
|
+
# == Table Aliasing
|
225
328
|
#
|
329
|
+
# ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
|
330
|
+
# the standard table name is used. The second time, the table is aliased as #{reflection_name}_#{parent_table_name}. Indexes are appended
|
331
|
+
# for any more successive uses of the table name.
|
332
|
+
#
|
333
|
+
# Post.find :all, :include => :comments
|
334
|
+
# # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ...
|
335
|
+
# Post.find :all, :include => :special_comments # STI
|
336
|
+
# # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... AND comments.type = 'SpecialComment'
|
337
|
+
# Post.find :all, :include => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
|
338
|
+
# # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... LEFT OUTER JOIN comments special_comments_posts
|
339
|
+
#
|
340
|
+
# Acts as tree example:
|
341
|
+
#
|
342
|
+
# TreeMixin.find :all, :include => :children
|
343
|
+
# # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
|
344
|
+
# TreeMixin.find :all, :include => {:children => :parent} # using cascading eager includes
|
345
|
+
# # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
|
346
|
+
# LEFT OUTER JOIN parents_mixins ...
|
347
|
+
# TreeMixin.find :all, :include => {:children => {:parent => :children}}
|
348
|
+
# # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
|
349
|
+
# LEFT OUTER JOIN parents_mixins ...
|
350
|
+
# LEFT OUTER JOIN mixins childrens_mixins_2
|
351
|
+
#
|
352
|
+
# Has and Belongs to Many join tables use the same idea, but add a _join suffix:
|
353
|
+
#
|
354
|
+
# Post.find :all, :include => :categories
|
355
|
+
# # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
|
356
|
+
# Post.find :all, :include => {:categories => :posts}
|
357
|
+
# # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
|
358
|
+
# LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
|
359
|
+
# Post.find :all, :include => {:categories => {:posts => :categories}}
|
360
|
+
# # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
|
361
|
+
# LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
|
362
|
+
# LEFT OUTER JOIN categories_posts categories_posts_join LEFT OUTER JOIN categories categories_posts
|
363
|
+
#
|
364
|
+
# If you wish to specify your own custom joins using a :joins option, those table names will take precedence over the eager associations..
|
365
|
+
#
|
366
|
+
# Post.find :all, :include => :comments, :joins => "inner join comments ..."
|
367
|
+
# # => SELECT ... FROM posts LEFT OUTER JOIN comments_posts ON ... INNER JOIN comments ...
|
368
|
+
# Post.find :all, :include => [:comments, :special_comments], :joins => "inner join comments ..."
|
369
|
+
# # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ...
|
370
|
+
# LEFT OUTER JOIN comments special_comments_posts ...
|
371
|
+
# INNER JOIN comments ...
|
372
|
+
#
|
373
|
+
# Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
|
374
|
+
#
|
226
375
|
# == Modules
|
227
376
|
#
|
228
377
|
# By default, associations will look for objects within the current module scope. Consider:
|
@@ -273,7 +422,7 @@ module ActiveRecord
|
|
273
422
|
# * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
|
274
423
|
# * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
|
275
424
|
# * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they
|
276
|
-
# are <tt>:dependent</tt>, deletes them directly from the database if they are <tt>:
|
425
|
+
# are <tt>:dependent</tt>, deletes them directly from the database if they are <tt>:dependent => :delete_all</tt>,
|
277
426
|
# and sets their foreign keys to NULL otherwise.
|
278
427
|
# * <tt>collection.empty?</tt> - returns true if there are no associated objects.
|
279
428
|
# * <tt>collection.size</tt> - returns the number of associated objects.
|
@@ -312,10 +461,11 @@ module ActiveRecord
|
|
312
461
|
# * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
|
313
462
|
# of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_many association will use "person_id"
|
314
463
|
# as the default foreign_key.
|
315
|
-
# * <tt>:dependent</tt> - if set to :destroy
|
464
|
+
# * <tt>:dependent</tt> - if set to :destroy all the associated objects are destroyed
|
316
465
|
# alongside this object by calling their destroy method. If set to :delete_all all associated
|
317
466
|
# objects are deleted *without* calling their destroy method. If set to :nullify all associated
|
318
467
|
# objects' foreign keys are set to NULL *without* calling their save callbacks.
|
468
|
+
# NOTE: :dependent => true is deprecated and has been replaced with :dependent => :destroy.
|
319
469
|
# May not be set if :exclusively_dependent is also set.
|
320
470
|
# * <tt>:exclusively_dependent</tt> - Deprecated; equivalent to :dependent => :delete_all. If set to true all
|
321
471
|
# the associated object are deleted in one SQL statement without having their
|
@@ -328,69 +478,46 @@ module ActiveRecord
|
|
328
478
|
# specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
|
329
479
|
# * <tt>:extend</tt> - specify a named module for extending the proxy, see "Association extensions".
|
330
480
|
# * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
|
481
|
+
# * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
|
482
|
+
# * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
|
483
|
+
# * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
|
484
|
+
# * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
|
485
|
+
# include the joined columns.
|
486
|
+
# * <tt>:as</tt>: Specifies a polymorphic interface (See #belongs_to).
|
487
|
+
# * <tt>:through</tt>: Specifies a Join Model to perform the query through. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
|
488
|
+
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
|
489
|
+
# or <tt>has_many</tt> association.
|
490
|
+
# * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
|
491
|
+
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or
|
492
|
+
# +:subscriber+ on +Subscription+, unless a +:source+ is given.
|
331
493
|
#
|
332
494
|
# Option examples:
|
333
495
|
# has_many :comments, :order => "posted_on"
|
334
496
|
# has_many :comments, :include => :author
|
335
497
|
# has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
|
336
|
-
# has_many :tracks, :order => "position", :dependent =>
|
498
|
+
# has_many :tracks, :order => "position", :dependent => :destroy
|
499
|
+
# has_many :comments, :dependent => :nullify
|
500
|
+
# has_many :tags, :as => :taggable
|
501
|
+
# has_many :subscribers, :through => :subscriptions, :source => :user
|
337
502
|
# has_many :subscribers, :class_name => "Person", :finder_sql =>
|
338
503
|
# 'SELECT DISTINCT people.* ' +
|
339
504
|
# 'FROM people p, post_subscriptions ps ' +
|
340
505
|
# 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
|
341
506
|
# 'ORDER BY p.first_name'
|
342
507
|
def has_many(association_id, options = {}, &extension)
|
343
|
-
options
|
344
|
-
:foreign_key, :class_name, :exclusively_dependent, :dependent,
|
345
|
-
:conditions, :order, :include, :finder_sql, :counter_sql,
|
346
|
-
:before_add, :after_add, :before_remove, :after_remove, :extend,
|
347
|
-
:group
|
348
|
-
)
|
349
|
-
|
350
|
-
options[:extend] = create_extension_module(association_id, extension) if block_given?
|
351
|
-
|
352
|
-
association_name, association_class_name, association_class_primary_key_name =
|
353
|
-
associate_identification(association_id, options[:class_name], options[:foreign_key])
|
354
|
-
|
355
|
-
require_association_class(association_class_name)
|
508
|
+
reflection = create_has_many_reflection(association_id, options, &extension)
|
356
509
|
|
357
|
-
|
510
|
+
configure_dependency_for_has_many(reflection)
|
358
511
|
|
359
|
-
if options[:
|
360
|
-
|
361
|
-
|
512
|
+
if options[:through]
|
513
|
+
collection_reader_method(reflection, HasManyThroughAssociation)
|
514
|
+
else
|
515
|
+
add_multiple_associated_save_callbacks(reflection.name)
|
516
|
+
add_association_callbacks(reflection.name, reflection.options)
|
517
|
+
collection_accessor_methods(reflection, HasManyAssociation)
|
362
518
|
end
|
363
519
|
|
364
|
-
|
365
|
-
# delete children, otherwise foreign key is set to NULL.
|
366
|
-
case options[:dependent]
|
367
|
-
when :destroy, true
|
368
|
-
module_eval "before_destroy '#{association_name}.each { |o| o.destroy }'"
|
369
|
-
when :delete_all
|
370
|
-
module_eval "before_destroy { |record| #{association_class_name}.delete_all(%(#{association_class_primary_key_name} = \#{record.quoted_id})) }"
|
371
|
-
when :nullify
|
372
|
-
module_eval "before_destroy { |record| #{association_class_name}.update_all(%(#{association_class_primary_key_name} = NULL), %(#{association_class_primary_key_name} = \#{record.quoted_id})) }"
|
373
|
-
when nil, false
|
374
|
-
# pass
|
375
|
-
else
|
376
|
-
raise ArgumentError, 'The :dependent option expects either true, :destroy, :delete_all, or :nullify'
|
377
|
-
end
|
378
|
-
|
379
|
-
|
380
|
-
add_multiple_associated_save_callbacks(association_name)
|
381
|
-
add_association_callbacks(association_name, options)
|
382
|
-
|
383
|
-
collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasManyAssociation)
|
384
|
-
|
385
|
-
# deprecated api
|
386
|
-
deprecated_collection_count_method(association_name)
|
387
|
-
deprecated_add_association_relation(association_name)
|
388
|
-
deprecated_remove_association_relation(association_name)
|
389
|
-
deprecated_has_collection_method(association_name)
|
390
|
-
deprecated_find_in_collection_method(association_name)
|
391
|
-
deprecated_find_all_in_collection_method(association_name)
|
392
|
-
deprecated_collection_create_method(association_name)
|
393
|
-
deprecated_collection_build_method(association_name)
|
520
|
+
add_deprecated_api_for_has_many(reflection.name)
|
394
521
|
end
|
395
522
|
|
396
523
|
# Adds the following methods for retrieval and query of a single associated object.
|
@@ -431,46 +558,32 @@ module ActiveRecord
|
|
431
558
|
# * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
|
432
559
|
#
|
433
560
|
# Option examples:
|
434
|
-
# has_one :credit_card, :dependent =>
|
561
|
+
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
|
562
|
+
# has_one :credit_card, :dependent => :nullify # updates the associated records foriegn key value to null rather than destroying it
|
435
563
|
# has_one :last_comment, :class_name => "Comment", :order => "posted_on"
|
436
564
|
# has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
|
437
565
|
def has_one(association_id, options = {})
|
438
|
-
|
439
|
-
|
440
|
-
association_name, association_class_name, association_class_primary_key_name =
|
441
|
-
associate_identification(association_id, options[:class_name], options[:foreign_key], false)
|
442
|
-
|
443
|
-
require_association_class(association_class_name)
|
566
|
+
reflection = create_has_one_reflection(association_id, options)
|
444
567
|
|
445
568
|
module_eval do
|
446
569
|
after_save <<-EOF
|
447
|
-
association = instance_variable_get("@#{
|
570
|
+
association = instance_variable_get("@#{reflection.name}")
|
448
571
|
unless association.nil?
|
449
|
-
association["#{
|
572
|
+
association["#{reflection.primary_key_name}"] = id
|
450
573
|
association.save(true)
|
451
|
-
association.send(:construct_sql)
|
452
574
|
end
|
453
575
|
EOF
|
454
576
|
end
|
455
577
|
|
456
|
-
association_accessor_methods(
|
457
|
-
association_constructor_method(:build,
|
458
|
-
association_constructor_method(:create,
|
578
|
+
association_accessor_methods(reflection, HasOneAssociation)
|
579
|
+
association_constructor_method(:build, reflection, HasOneAssociation)
|
580
|
+
association_constructor_method(:create, reflection, HasOneAssociation)
|
459
581
|
|
460
|
-
|
461
|
-
when :destroy, true
|
462
|
-
module_eval "before_destroy '#{association_name}.destroy unless #{association_name}.nil?'"
|
463
|
-
when :nullify
|
464
|
-
module_eval "before_destroy '#{association_name}.update_attribute(\"#{association_class_primary_key_name}\", nil)'"
|
465
|
-
when nil, false
|
466
|
-
# pass
|
467
|
-
else
|
468
|
-
raise ArgumentError, "The :dependent option expects either :destroy or :nullify."
|
469
|
-
end
|
582
|
+
configure_dependency_for_has_one(reflection)
|
470
583
|
|
471
584
|
# deprecated api
|
472
|
-
deprecated_has_association_method(
|
473
|
-
deprecated_association_comparison_method(
|
585
|
+
deprecated_has_association_method(reflection.name)
|
586
|
+
deprecated_association_comparison_method(reflection.name, reflection.class_name)
|
474
587
|
end
|
475
588
|
|
476
589
|
# Adds the following methods for retrieval and query for a single associated object that this object holds an id to.
|
@@ -507,56 +620,78 @@ module ActiveRecord
|
|
507
620
|
# * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through use of increment_counter
|
508
621
|
# and decrement_counter. The counter cache is incremented when an object of this class is created and decremented when it's
|
509
622
|
# destroyed. This requires that a column named "#{table_name}_count" (such as comments_count for a belonging Comment class)
|
510
|
-
# is used on the associate class (such as a Post class).
|
623
|
+
# is used on the associate class (such as a Post class). You can also specify a custom counter cache column by given that
|
624
|
+
# name instead of a true/false value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
|
511
625
|
# * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
|
626
|
+
# # <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing true.
|
512
627
|
#
|
513
628
|
# Option examples:
|
514
629
|
# belongs_to :firm, :foreign_key => "client_of"
|
515
630
|
# belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
|
516
631
|
# belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
|
517
632
|
# :conditions => 'discounts > #{payments_count}'
|
633
|
+
# belongs_to :attachable, :polymorphic => true
|
518
634
|
def belongs_to(association_id, options = {})
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
if association.new_record?
|
537
|
-
association.save(true)
|
538
|
-
association.send(:construct_sql)
|
635
|
+
reflection = create_belongs_to_reflection(association_id, options)
|
636
|
+
|
637
|
+
if reflection.options[:polymorphic]
|
638
|
+
association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
|
639
|
+
|
640
|
+
module_eval do
|
641
|
+
before_save <<-EOF
|
642
|
+
association = instance_variable_get("@#{reflection.name}")
|
643
|
+
if !association.nil?
|
644
|
+
if association.new_record?
|
645
|
+
association.save(true)
|
646
|
+
end
|
647
|
+
|
648
|
+
if association.updated?
|
649
|
+
self["#{reflection.primary_key_name}"] = association.id
|
650
|
+
self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
|
651
|
+
end
|
539
652
|
end
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
653
|
+
EOF
|
654
|
+
end
|
655
|
+
else
|
656
|
+
association_accessor_methods(reflection, BelongsToAssociation)
|
657
|
+
association_constructor_method(:build, reflection, BelongsToAssociation)
|
658
|
+
association_constructor_method(:create, reflection, BelongsToAssociation)
|
659
|
+
|
660
|
+
module_eval do
|
661
|
+
before_save <<-EOF
|
662
|
+
association = instance_variable_get("@#{reflection.name}")
|
663
|
+
if !association.nil?
|
664
|
+
if association.new_record?
|
665
|
+
association.save(true)
|
666
|
+
end
|
667
|
+
|
668
|
+
if association.updated?
|
669
|
+
self["#{reflection.primary_key_name}"] = association.id
|
670
|
+
end
|
671
|
+
end
|
672
|
+
EOF
|
673
|
+
end
|
544
674
|
|
675
|
+
# deprecated api
|
676
|
+
deprecated_has_association_method(reflection.name)
|
677
|
+
deprecated_association_comparison_method(reflection.name, reflection.class_name)
|
678
|
+
end
|
679
|
+
|
545
680
|
if options[:counter_cache]
|
681
|
+
cache_column = options[:counter_cache] == true ?
|
682
|
+
"#{self.to_s.underscore.pluralize}_count" :
|
683
|
+
options[:counter_cache]
|
684
|
+
|
546
685
|
module_eval(
|
547
|
-
"after_create '#{
|
548
|
-
" unless #{
|
686
|
+
"after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
|
687
|
+
" unless #{reflection.name}.nil?'"
|
549
688
|
)
|
550
689
|
|
551
690
|
module_eval(
|
552
|
-
"before_destroy '#{
|
553
|
-
" unless #{
|
691
|
+
"before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
|
692
|
+
" unless #{reflection.name}.nil?'"
|
554
693
|
)
|
555
694
|
end
|
556
|
-
|
557
|
-
# deprecated api
|
558
|
-
deprecated_has_association_method(association_name)
|
559
|
-
deprecated_association_comparison_method(association_name, association_class_name)
|
560
695
|
end
|
561
696
|
|
562
697
|
# Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
|
@@ -627,6 +762,11 @@ module ActiveRecord
|
|
627
762
|
# with a manual one
|
628
763
|
# * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions".
|
629
764
|
# * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
|
765
|
+
# * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
|
766
|
+
# * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
|
767
|
+
# * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
|
768
|
+
# * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
|
769
|
+
# include the joined columns.
|
630
770
|
#
|
631
771
|
# Option examples:
|
632
772
|
# has_and_belongs_to_many :projects
|
@@ -636,43 +776,29 @@ module ActiveRecord
|
|
636
776
|
# has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
|
637
777
|
# 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
|
638
778
|
def has_and_belongs_to_many(association_id, options = {}, &extension)
|
639
|
-
options
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
)
|
644
|
-
|
645
|
-
options[:extend] = create_extension_module(association_id, extension) if block_given?
|
646
|
-
|
647
|
-
association_name, association_class_name, association_class_primary_key_name =
|
648
|
-
associate_identification(association_id, options[:class_name], options[:foreign_key])
|
649
|
-
|
650
|
-
require_association_class(association_class_name)
|
651
|
-
|
652
|
-
options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(association_class_name))
|
653
|
-
|
654
|
-
add_multiple_associated_save_callbacks(association_name)
|
655
|
-
|
656
|
-
collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasAndBelongsToManyAssociation)
|
779
|
+
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
|
780
|
+
|
781
|
+
add_multiple_associated_save_callbacks(reflection.name)
|
782
|
+
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
|
657
783
|
|
658
784
|
# Don't use a before_destroy callback since users' before_destroy
|
659
785
|
# callbacks will be executed after the association is wiped out.
|
660
|
-
old_method = "destroy_without_habtm_shim_for_#{
|
786
|
+
old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
|
661
787
|
class_eval <<-end_eval
|
662
788
|
alias_method :#{old_method}, :destroy_without_callbacks
|
663
789
|
def destroy_without_callbacks
|
664
|
-
#{
|
790
|
+
#{reflection.name}.clear
|
665
791
|
#{old_method}
|
666
792
|
end
|
667
793
|
end_eval
|
668
794
|
|
669
|
-
add_association_callbacks(
|
795
|
+
add_association_callbacks(reflection.name, options)
|
670
796
|
|
671
797
|
# deprecated api
|
672
|
-
deprecated_collection_count_method(
|
673
|
-
deprecated_add_association_relation(
|
674
|
-
deprecated_remove_association_relation(
|
675
|
-
deprecated_has_collection_method(
|
798
|
+
deprecated_collection_count_method(reflection.name)
|
799
|
+
deprecated_add_association_relation(reflection.name)
|
800
|
+
deprecated_remove_association_relation(reflection.name)
|
801
|
+
deprecated_has_collection_method(reflection.name)
|
676
802
|
end
|
677
803
|
|
678
804
|
private
|
@@ -686,93 +812,81 @@ module ActiveRecord
|
|
686
812
|
table_name_prefix + join_table + table_name_suffix
|
687
813
|
end
|
688
814
|
|
689
|
-
def
|
690
|
-
|
691
|
-
association_class_name = type_name_with_module(
|
692
|
-
association_class_name ||
|
693
|
-
Inflector.camelize(plural ? Inflector.singularize(association_id.id2name) : association_id.id2name)
|
694
|
-
)
|
695
|
-
end
|
696
|
-
|
697
|
-
primary_key_name = foreign_key || name.foreign_key
|
698
|
-
|
699
|
-
return association_id.id2name, association_class_name, primary_key_name
|
700
|
-
end
|
701
|
-
|
702
|
-
def association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, association_proxy_class)
|
703
|
-
define_method(association_name) do |*params|
|
815
|
+
def association_accessor_methods(reflection, association_proxy_class)
|
816
|
+
define_method(reflection.name) do |*params|
|
704
817
|
force_reload = params.first unless params.empty?
|
705
|
-
association = instance_variable_get("@#{
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
association_class_primary_key_name, options)
|
818
|
+
association = instance_variable_get("@#{reflection.name}")
|
819
|
+
|
820
|
+
if association.nil? || force_reload
|
821
|
+
association = association_proxy_class.new(self, reflection)
|
710
822
|
retval = association.reload
|
711
823
|
unless retval.nil?
|
712
|
-
instance_variable_set("@#{
|
824
|
+
instance_variable_set("@#{reflection.name}", association)
|
713
825
|
else
|
714
|
-
instance_variable_set("@#{
|
826
|
+
instance_variable_set("@#{reflection.name}", nil)
|
715
827
|
return nil
|
716
828
|
end
|
717
829
|
end
|
718
830
|
association
|
719
831
|
end
|
720
832
|
|
721
|
-
define_method("#{
|
722
|
-
association = instance_variable_get("@#{
|
833
|
+
define_method("#{reflection.name}=") do |new_value|
|
834
|
+
association = instance_variable_get("@#{reflection.name}")
|
723
835
|
if association.nil?
|
724
|
-
association = association_proxy_class.new(self,
|
725
|
-
association_name, association_class_name,
|
726
|
-
association_class_primary_key_name, options)
|
836
|
+
association = association_proxy_class.new(self, reflection)
|
727
837
|
end
|
838
|
+
|
728
839
|
association.replace(new_value)
|
840
|
+
|
729
841
|
unless new_value.nil?
|
730
|
-
instance_variable_set("@#{
|
842
|
+
instance_variable_set("@#{reflection.name}", association)
|
731
843
|
else
|
732
|
-
instance_variable_set("@#{
|
844
|
+
instance_variable_set("@#{reflection.name}", nil)
|
733
845
|
return nil
|
734
846
|
end
|
847
|
+
|
735
848
|
association
|
736
849
|
end
|
737
850
|
|
738
|
-
define_method("set_#{
|
851
|
+
define_method("set_#{reflection.name}_target") do |target|
|
739
852
|
return if target.nil?
|
740
|
-
association = association_proxy_class.new(self,
|
741
|
-
association_name, association_class_name,
|
742
|
-
association_class_primary_key_name, options)
|
853
|
+
association = association_proxy_class.new(self, reflection)
|
743
854
|
association.target = target
|
744
|
-
instance_variable_set("@#{
|
855
|
+
instance_variable_set("@#{reflection.name}", association)
|
745
856
|
end
|
746
857
|
end
|
747
858
|
|
748
|
-
def
|
749
|
-
define_method(
|
859
|
+
def collection_reader_method(reflection, association_proxy_class)
|
860
|
+
define_method(reflection.name) do |*params|
|
750
861
|
force_reload = params.first unless params.empty?
|
751
|
-
association = instance_variable_get("@#{
|
862
|
+
association = instance_variable_get("@#{reflection.name}")
|
863
|
+
|
752
864
|
unless association.respond_to?(:loaded?)
|
753
|
-
association = association_proxy_class.new(self,
|
754
|
-
|
755
|
-
association_class_primary_key_name, options)
|
756
|
-
instance_variable_set("@#{association_name}", association)
|
865
|
+
association = association_proxy_class.new(self, reflection)
|
866
|
+
instance_variable_set("@#{reflection.name}", association)
|
757
867
|
end
|
868
|
+
|
758
869
|
association.reload if force_reload
|
870
|
+
|
759
871
|
association
|
760
872
|
end
|
873
|
+
end
|
761
874
|
|
762
|
-
|
763
|
-
|
875
|
+
def collection_accessor_methods(reflection, association_proxy_class)
|
876
|
+
collection_reader_method(reflection, association_proxy_class)
|
877
|
+
|
878
|
+
define_method("#{reflection.name}=") do |new_value|
|
879
|
+
association = instance_variable_get("@#{reflection.name}")
|
764
880
|
unless association.respond_to?(:loaded?)
|
765
|
-
association = association_proxy_class.new(self,
|
766
|
-
|
767
|
-
association_class_primary_key_name, options)
|
768
|
-
instance_variable_set("@#{association_name}", association)
|
881
|
+
association = association_proxy_class.new(self, reflection)
|
882
|
+
instance_variable_set("@#{reflection.name}", association)
|
769
883
|
end
|
770
884
|
association.replace(new_value)
|
771
885
|
association
|
772
886
|
end
|
773
887
|
|
774
|
-
define_method("#{
|
775
|
-
send("#{
|
888
|
+
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
|
889
|
+
send("#{reflection.name}=", reflection.class_name.constantize.find(new_value))
|
776
890
|
end
|
777
891
|
end
|
778
892
|
|
@@ -783,7 +897,6 @@ module ActiveRecord
|
|
783
897
|
def add_multiple_associated_save_callbacks(association_name)
|
784
898
|
method_name = "validate_associated_records_for_#{association_name}".to_sym
|
785
899
|
define_method(method_name) do
|
786
|
-
@new_record_before_save = new_record?
|
787
900
|
association = instance_variable_get("@#{association_name}")
|
788
901
|
if association.respond_to?(:loaded?)
|
789
902
|
if new_record?
|
@@ -797,9 +910,11 @@ module ActiveRecord
|
|
797
910
|
end
|
798
911
|
|
799
912
|
validate method_name
|
913
|
+
before_save("@new_record_before_save = new_record?; true")
|
800
914
|
|
801
915
|
after_callback = <<-end_eval
|
802
916
|
association = instance_variable_get("@#{association_name}")
|
917
|
+
|
803
918
|
if association.respond_to?(:loaded?)
|
804
919
|
if @new_record_before_save
|
805
920
|
records_to_save = association
|
@@ -809,27 +924,22 @@ module ActiveRecord
|
|
809
924
|
records_to_save.each { |record| association.send(:insert_record, record) }
|
810
925
|
association.send(:construct_sql) # reconstruct the SQL queries now that we know the owner's id
|
811
926
|
end
|
812
|
-
|
813
|
-
@new_record_before_save = false
|
814
|
-
true
|
815
927
|
end_eval
|
816
|
-
|
928
|
+
|
817
929
|
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
818
930
|
after_create(after_callback)
|
819
931
|
after_update(after_callback)
|
820
932
|
end
|
821
933
|
|
822
|
-
def association_constructor_method(constructor,
|
823
|
-
define_method("#{constructor}_#{
|
934
|
+
def association_constructor_method(constructor, reflection, association_proxy_class)
|
935
|
+
define_method("#{constructor}_#{reflection.name}") do |*params|
|
824
936
|
attributees = params.first unless params.empty?
|
825
937
|
replace_existing = params[1].nil? ? true : params[1]
|
826
|
-
association = instance_variable_get("@#{
|
938
|
+
association = instance_variable_get("@#{reflection.name}")
|
827
939
|
|
828
940
|
if association.nil?
|
829
|
-
association = association_proxy_class.new(self,
|
830
|
-
|
831
|
-
association_class_primary_key_name, options)
|
832
|
-
instance_variable_set("@#{association_name}", association)
|
941
|
+
association = association_proxy_class.new(self, reflection)
|
942
|
+
instance_variable_set("@#{reflection.name}", association)
|
833
943
|
end
|
834
944
|
|
835
945
|
if association_proxy_class == HasOneAssociation
|
@@ -839,63 +949,137 @@ module ActiveRecord
|
|
839
949
|
end
|
840
950
|
end
|
841
951
|
end
|
952
|
+
|
953
|
+
def count_with_associations(options = {})
|
954
|
+
join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
|
955
|
+
return count_by_sql(construct_counter_sql_with_included_associations(options, join_dependency))
|
956
|
+
end
|
842
957
|
|
843
958
|
def find_with_associations(options = {})
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
schema_abbreviations = generate_schema_abbreviations(reflections)
|
849
|
-
primary_key_table = generate_primary_key_table(reflections, schema_abbreviations)
|
959
|
+
join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
|
960
|
+
rows = select_all_rows(options, join_dependency)
|
961
|
+
return join_dependency.instantiate(rows)
|
962
|
+
end
|
850
963
|
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
for row in rows
|
856
|
-
id = row[primary_key]
|
857
|
-
records_in_order << (records[id] = instantiate(extract_record(schema_abbreviations, table_name, row))) unless records[id]
|
858
|
-
record = records[id]
|
964
|
+
def configure_dependency_for_has_many(reflection)
|
965
|
+
if reflection.options[:dependent] && reflection.options[:exclusively_dependent]
|
966
|
+
raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.'
|
967
|
+
end
|
859
968
|
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
collection.loaded
|
969
|
+
if reflection.options[:exclusively_dependent]
|
970
|
+
reflection.options[:dependent] = :delete_all
|
971
|
+
#warn "The :exclusively_dependent option is deprecated. Please use :dependent => :delete_all instead.")
|
972
|
+
end
|
865
973
|
|
866
|
-
|
974
|
+
# See HasManyAssociation#delete_records. Dependent associations
|
975
|
+
# delete children, otherwise foreign key is set to NULL.
|
867
976
|
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
977
|
+
# Add polymorphic type if the :as option is present
|
978
|
+
dependent_conditions = %(#{reflection.primary_key_name} = \#{record.quoted_id})
|
979
|
+
if reflection.options[:as]
|
980
|
+
dependent_conditions += " AND #{reflection.options[:as]}_type = '#{base_class.name}'"
|
981
|
+
end
|
872
982
|
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
983
|
+
case reflection.options[:dependent]
|
984
|
+
when :destroy, true
|
985
|
+
module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
|
986
|
+
when :delete_all
|
987
|
+
module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
|
988
|
+
when :nullify
|
989
|
+
module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }"
|
990
|
+
when nil, false
|
991
|
+
# pass
|
992
|
+
else
|
993
|
+
raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, or :nullify'
|
994
|
+
end
|
995
|
+
end
|
996
|
+
|
997
|
+
def configure_dependency_for_has_one(reflection)
|
998
|
+
case reflection.options[:dependent]
|
999
|
+
when :destroy, true
|
1000
|
+
module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
|
1001
|
+
when :nullify
|
1002
|
+
module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'"
|
1003
|
+
when nil, false
|
1004
|
+
# pass
|
1005
|
+
else
|
1006
|
+
raise ArgumentError, "The :dependent option expects either :destroy or :nullify."
|
879
1007
|
end
|
880
|
-
|
881
|
-
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
|
1011
|
+
def add_deprecated_api_for_has_many(association_name)
|
1012
|
+
deprecated_collection_count_method(association_name)
|
1013
|
+
deprecated_add_association_relation(association_name)
|
1014
|
+
deprecated_remove_association_relation(association_name)
|
1015
|
+
deprecated_has_collection_method(association_name)
|
1016
|
+
deprecated_find_in_collection_method(association_name)
|
1017
|
+
deprecated_find_all_in_collection_method(association_name)
|
1018
|
+
deprecated_collection_create_method(association_name)
|
1019
|
+
deprecated_collection_build_method(association_name)
|
882
1020
|
end
|
883
1021
|
|
1022
|
+
def create_has_many_reflection(association_id, options, &extension)
|
1023
|
+
options.assert_valid_keys(
|
1024
|
+
:class_name, :table_name, :foreign_key,
|
1025
|
+
:exclusively_dependent, :dependent,
|
1026
|
+
:select, :conditions, :include, :order, :group, :limit, :offset,
|
1027
|
+
:as, :through, :source,
|
1028
|
+
:finder_sql, :counter_sql,
|
1029
|
+
:before_add, :after_add, :before_remove, :after_remove,
|
1030
|
+
:extend
|
1031
|
+
)
|
884
1032
|
|
885
|
-
|
886
|
-
|
1033
|
+
options[:extend] = create_extension_module(association_id, extension) if block_given?
|
1034
|
+
|
1035
|
+
create_reflection(:has_many, association_id, options, self)
|
887
1036
|
end
|
888
1037
|
|
889
|
-
def
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
1038
|
+
def create_has_one_reflection(association_id, options)
|
1039
|
+
options.assert_valid_keys(
|
1040
|
+
:class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as
|
1041
|
+
)
|
1042
|
+
|
1043
|
+
create_reflection(:has_one, association_id, options, self)
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
def create_belongs_to_reflection(association_id, options)
|
1047
|
+
options.assert_valid_keys(
|
1048
|
+
:class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
|
1049
|
+
:counter_cache, :extend, :polymorphic
|
1050
|
+
)
|
1051
|
+
|
1052
|
+
reflection = create_reflection(:belongs_to, association_id, options, self)
|
1053
|
+
|
1054
|
+
if options[:polymorphic]
|
1055
|
+
reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
|
896
1056
|
end
|
1057
|
+
|
1058
|
+
reflection
|
897
1059
|
end
|
898
1060
|
|
1061
|
+
def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
|
1062
|
+
options.assert_valid_keys(
|
1063
|
+
:class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
|
1064
|
+
:select, :conditions, :include, :order, :group, :limit, :offset,
|
1065
|
+
:finder_sql, :delete_sql, :insert_sql, :uniq,
|
1066
|
+
:before_add, :after_add, :before_remove, :after_remove,
|
1067
|
+
:extend
|
1068
|
+
)
|
1069
|
+
|
1070
|
+
options[:extend] = create_extension_module(association_id, extension) if block_given?
|
1071
|
+
|
1072
|
+
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
|
1073
|
+
|
1074
|
+
reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
|
1075
|
+
|
1076
|
+
reflection
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
def reflect_on_included_associations(associations)
|
1080
|
+
[ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) }
|
1081
|
+
end
|
1082
|
+
|
899
1083
|
def guard_against_unlimitable_reflections(reflections, options)
|
900
1084
|
if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections)
|
901
1085
|
raise(
|
@@ -905,127 +1089,109 @@ module ActiveRecord
|
|
905
1089
|
end
|
906
1090
|
end
|
907
1091
|
|
908
|
-
def
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
1092
|
+
def select_all_rows(options, join_dependency)
|
1093
|
+
connection.select_all(
|
1094
|
+
construct_finder_sql_with_included_associations(options, join_dependency),
|
1095
|
+
"#{name} Load Including Associations"
|
1096
|
+
)
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
def construct_counter_sql_with_included_associations(options, join_dependency)
|
1100
|
+
scope = scope(:find)
|
1101
|
+
sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key})"
|
1102
|
+
|
1103
|
+
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
|
1104
|
+
if !Base.connection.supports_count_distinct?
|
1105
|
+
sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{table_name}.#{primary_key}"
|
916
1106
|
end
|
917
1107
|
|
918
|
-
|
919
|
-
|
1108
|
+
sql << " FROM #{table_name} "
|
1109
|
+
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
1110
|
+
|
1111
|
+
add_joins!(sql, options, scope)
|
1112
|
+
add_conditions!(sql, options[:conditions], scope)
|
1113
|
+
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
920
1114
|
|
921
|
-
|
922
|
-
primary_key_lookup_table = {}
|
923
|
-
primary_key_lookup_table[table_name] =
|
924
|
-
schema_abbreviations.find { |cn, tc| tc == [ table_name, primary_key ] }.first
|
1115
|
+
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
925
1116
|
|
926
|
-
|
927
|
-
|
928
|
-
tc == [ reflection.klass.table_name, reflection.klass.primary_key ]
|
929
|
-
}.first
|
1117
|
+
if !Base.connection.supports_count_distinct?
|
1118
|
+
sql << ")"
|
930
1119
|
end
|
931
|
-
|
932
|
-
return primary_key_lookup_table
|
933
|
-
end
|
934
|
-
|
935
1120
|
|
936
|
-
|
937
|
-
connection.select_all(
|
938
|
-
construct_finder_sql_with_included_associations(options, schema_abbreviations, reflections),
|
939
|
-
"#{name} Load Including Associations"
|
940
|
-
)
|
1121
|
+
return sanitize_sql(sql)
|
941
1122
|
end
|
942
1123
|
|
943
|
-
def construct_finder_sql_with_included_associations(options,
|
944
|
-
|
945
|
-
sql
|
946
|
-
sql <<
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
add_limited_ids_condition!(sql, options) if !using_limitable_reflections?(reflections) && options[:limit]
|
1124
|
+
def construct_finder_sql_with_included_associations(options, join_dependency)
|
1125
|
+
scope = scope(:find)
|
1126
|
+
sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || table_name} "
|
1127
|
+
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
1128
|
+
|
1129
|
+
add_joins!(sql, options, scope)
|
1130
|
+
add_conditions!(sql, options[:conditions], scope)
|
1131
|
+
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]
|
951
1132
|
|
952
1133
|
sql << "ORDER BY #{options[:order]} " if options[:order]
|
953
|
-
|
954
|
-
add_limit!(sql, options) if using_limitable_reflections?(reflections)
|
955
|
-
|
1134
|
+
|
1135
|
+
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
1136
|
+
|
956
1137
|
return sanitize_sql(sql)
|
957
1138
|
end
|
958
|
-
|
959
|
-
def add_limited_ids_condition!(sql, options)
|
960
|
-
unless (id_list = select_limited_ids_list(options)).empty?
|
1139
|
+
|
1140
|
+
def add_limited_ids_condition!(sql, options, join_dependency)
|
1141
|
+
unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
|
961
1142
|
sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) "
|
962
1143
|
end
|
963
1144
|
end
|
964
|
-
|
965
|
-
def select_limited_ids_list(options)
|
1145
|
+
|
1146
|
+
def select_limited_ids_list(options, join_dependency)
|
966
1147
|
connection.select_values(
|
967
|
-
construct_finder_sql_for_association_limiting(options),
|
1148
|
+
construct_finder_sql_for_association_limiting(options, join_dependency),
|
968
1149
|
"#{name} Load IDs For Limited Eager Loading"
|
969
1150
|
).collect { |id| connection.quote(id) }.join(", ")
|
970
1151
|
end
|
971
|
-
|
972
|
-
def construct_finder_sql_for_association_limiting(options)
|
973
|
-
|
1152
|
+
|
1153
|
+
def construct_finder_sql_for_association_limiting(options, join_dependency)
|
1154
|
+
scope = scope(:find)
|
1155
|
+
#sql = "SELECT DISTINCT #{table_name}.#{primary_key} FROM #{table_name} "
|
1156
|
+
sql = "SELECT "
|
1157
|
+
sql << "DISTINCT #{table_name}." if include_eager_conditions?(options) || include_eager_order?(options)
|
1158
|
+
sql << "#{primary_key} FROM #{table_name} "
|
1159
|
+
|
1160
|
+
if include_eager_conditions?(options) || include_eager_order?(options)
|
1161
|
+
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
1162
|
+
add_joins!(sql, options, scope)
|
1163
|
+
end
|
974
1164
|
|
975
|
-
sql
|
976
|
-
add_conditions!(sql, options[:conditions])
|
1165
|
+
add_conditions!(sql, options[:conditions], scope)
|
977
1166
|
sql << "ORDER BY #{options[:order]} " if options[:order]
|
978
|
-
add_limit!(sql, options)
|
1167
|
+
add_limit!(sql, options, scope)
|
979
1168
|
return sanitize_sql(sql)
|
980
1169
|
end
|
981
1170
|
|
982
1171
|
def include_eager_conditions?(options)
|
983
|
-
conditions = options[:conditions]
|
1172
|
+
conditions = scope(:find, :conditions) || options[:conditions]
|
984
1173
|
return false unless conditions
|
985
1174
|
conditions = conditions.first if conditions.is_a?(Array)
|
986
1175
|
conditions.scan(/(\w+)\.\w+/).flatten.any? do |condition_table_name|
|
987
1176
|
condition_table_name != table_name
|
988
1177
|
end
|
989
1178
|
end
|
990
|
-
|
991
|
-
def
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
sti_conditions = reflections.collect do |reflection|
|
997
|
-
reflection.klass.send(:type_condition) unless reflection.klass.descends_from_active_record?
|
998
|
-
end.compact
|
999
|
-
|
1000
|
-
unless sti_conditions.empty?
|
1001
|
-
sql << condition_word(sql) + sti_conditions.join(" AND ")
|
1179
|
+
|
1180
|
+
def include_eager_order?(options)
|
1181
|
+
order = options[:order]
|
1182
|
+
return false unless order
|
1183
|
+
order.scan(/(\w+)\.\w+/).flatten.any? do |order_table_name|
|
1184
|
+
order_table_name != table_name
|
1002
1185
|
end
|
1003
1186
|
end
|
1004
1187
|
|
1005
|
-
def
|
1006
|
-
|
1188
|
+
def using_limitable_reflections?(reflections)
|
1189
|
+
reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
|
1007
1190
|
end
|
1008
1191
|
|
1009
|
-
def
|
1010
|
-
|
1011
|
-
|
1012
|
-
" LEFT OUTER JOIN #{reflection.options[:join_table]} ON " +
|
1013
|
-
"#{reflection.options[:join_table]}.#{reflection.options[:foreign_key] || table_name.classify.foreign_key} = " +
|
1014
|
-
"#{table_name}.#{primary_key} " +
|
1015
|
-
" LEFT OUTER JOIN #{reflection.klass.table_name} ON " +
|
1016
|
-
"#{reflection.options[:join_table]}.#{reflection.options[:association_foreign_key] || reflection.klass.table_name.classify.foreign_key} = " +
|
1017
|
-
"#{reflection.klass.table_name}.#{reflection.klass.primary_key} "
|
1018
|
-
when :has_many, :has_one
|
1019
|
-
" LEFT OUTER JOIN #{reflection.klass.table_name} ON " +
|
1020
|
-
"#{reflection.klass.table_name}.#{reflection.options[:foreign_key] || table_name.classify.foreign_key} = " +
|
1021
|
-
"#{table_name}.#{primary_key} "
|
1022
|
-
when :belongs_to
|
1023
|
-
" LEFT OUTER JOIN #{reflection.klass.table_name} ON " +
|
1024
|
-
"#{reflection.klass.table_name}.#{reflection.klass.primary_key} = " +
|
1025
|
-
"#{table_name}.#{reflection.options[:foreign_key] || reflection.klass.table_name.classify.foreign_key} "
|
1026
|
-
else
|
1027
|
-
""
|
1028
|
-
end
|
1192
|
+
def column_aliases(join_dependency)
|
1193
|
+
join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
|
1194
|
+
"#{join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
|
1029
1195
|
end
|
1030
1196
|
|
1031
1197
|
def add_association_callbacks(association_name, options)
|
@@ -1040,15 +1206,6 @@ module ActiveRecord
|
|
1040
1206
|
end
|
1041
1207
|
end
|
1042
1208
|
|
1043
|
-
def extract_record(schema_abbreviations, table_name, row)
|
1044
|
-
record = {}
|
1045
|
-
row.each do |column, value|
|
1046
|
-
prefix, column_name = schema_abbreviations[column]
|
1047
|
-
record[column_name] = value if prefix == table_name
|
1048
|
-
end
|
1049
|
-
return record
|
1050
|
-
end
|
1051
|
-
|
1052
1209
|
def condition_word(sql)
|
1053
1210
|
sql =~ /where/i ? " AND " : "WHERE "
|
1054
1211
|
end
|
@@ -1062,6 +1219,295 @@ module ActiveRecord
|
|
1062
1219
|
|
1063
1220
|
extension_module_name.constantize
|
1064
1221
|
end
|
1222
|
+
|
1223
|
+
class JoinDependency
|
1224
|
+
attr_reader :joins, :reflections, :table_aliases
|
1225
|
+
|
1226
|
+
def initialize(base, associations, joins)
|
1227
|
+
@joins = [JoinBase.new(base, joins)]
|
1228
|
+
@associations = associations
|
1229
|
+
@reflections = []
|
1230
|
+
@base_records_hash = {}
|
1231
|
+
@base_records_in_order = []
|
1232
|
+
@table_aliases = Hash.new { |aliases, table| aliases[table] = 0 }
|
1233
|
+
@table_aliases[base.table_name] = 1
|
1234
|
+
build(associations)
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
def join_associations
|
1238
|
+
@joins[1..-1].to_a
|
1239
|
+
end
|
1240
|
+
|
1241
|
+
def join_base
|
1242
|
+
@joins[0]
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
def instantiate(rows)
|
1246
|
+
rows.each_with_index do |row, i|
|
1247
|
+
primary_id = join_base.record_id(row)
|
1248
|
+
unless @base_records_hash[primary_id]
|
1249
|
+
@base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
|
1250
|
+
end
|
1251
|
+
construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
|
1252
|
+
end
|
1253
|
+
return @base_records_in_order
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
def aliased_table_names_for(table_name)
|
1257
|
+
joins.select{|join| join.table_name == table_name }.collect{|join| join.aliased_table_name}
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
protected
|
1261
|
+
def build(associations, parent = nil)
|
1262
|
+
parent ||= @joins.last
|
1263
|
+
case associations
|
1264
|
+
when Symbol, String
|
1265
|
+
reflection = parent.reflections[associations.to_s.intern] or
|
1266
|
+
raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
|
1267
|
+
@reflections << reflection
|
1268
|
+
@joins << JoinAssociation.new(reflection, self, parent)
|
1269
|
+
when Array
|
1270
|
+
associations.each do |association|
|
1271
|
+
build(association, parent)
|
1272
|
+
end
|
1273
|
+
when Hash
|
1274
|
+
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
|
1275
|
+
build(name, parent)
|
1276
|
+
build(associations[name])
|
1277
|
+
end
|
1278
|
+
else
|
1279
|
+
raise ConfigurationError, associations.inspect
|
1280
|
+
end
|
1281
|
+
end
|
1282
|
+
|
1283
|
+
def construct(parent, associations, joins, row)
|
1284
|
+
case associations
|
1285
|
+
when Symbol, String
|
1286
|
+
while (join = joins.shift).reflection.name.to_s != associations.to_s
|
1287
|
+
raise ConfigurationError, "Not Enough Associations" if joins.empty?
|
1288
|
+
end
|
1289
|
+
construct_association(parent, join, row)
|
1290
|
+
when Array
|
1291
|
+
associations.each do |association|
|
1292
|
+
construct(parent, association, joins, row)
|
1293
|
+
end
|
1294
|
+
when Hash
|
1295
|
+
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
|
1296
|
+
association = construct_association(parent, joins.shift, row)
|
1297
|
+
construct(association, associations[name], joins, row) if association
|
1298
|
+
end
|
1299
|
+
else
|
1300
|
+
raise ConfigurationError, associations.inspect
|
1301
|
+
end
|
1302
|
+
end
|
1303
|
+
|
1304
|
+
def construct_association(record, join, row)
|
1305
|
+
case join.reflection.macro
|
1306
|
+
when :has_many, :has_and_belongs_to_many
|
1307
|
+
collection = record.send(join.reflection.name)
|
1308
|
+
collection.loaded
|
1309
|
+
|
1310
|
+
return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
|
1311
|
+
association = join.instantiate(row)
|
1312
|
+
collection.target.push(association) unless collection.target.include?(association)
|
1313
|
+
when :has_one, :belongs_to
|
1314
|
+
return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
|
1315
|
+
association = join.instantiate(row)
|
1316
|
+
record.send("set_#{join.reflection.name}_target", association)
|
1317
|
+
else
|
1318
|
+
raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
|
1319
|
+
end
|
1320
|
+
return association
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
class JoinBase
|
1324
|
+
attr_reader :active_record, :table_joins
|
1325
|
+
delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
|
1326
|
+
|
1327
|
+
def initialize(active_record, joins = nil)
|
1328
|
+
@active_record = active_record
|
1329
|
+
@cached_record = {}
|
1330
|
+
@table_joins = joins
|
1331
|
+
end
|
1332
|
+
|
1333
|
+
def aliased_prefix
|
1334
|
+
"t0"
|
1335
|
+
end
|
1336
|
+
|
1337
|
+
def aliased_primary_key
|
1338
|
+
"#{ aliased_prefix }_r0"
|
1339
|
+
end
|
1340
|
+
|
1341
|
+
def aliased_table_name
|
1342
|
+
active_record.table_name
|
1343
|
+
end
|
1344
|
+
|
1345
|
+
def column_names_with_alias
|
1346
|
+
unless @column_names_with_alias
|
1347
|
+
@column_names_with_alias = []
|
1348
|
+
([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
|
1349
|
+
@column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
|
1350
|
+
end
|
1351
|
+
end
|
1352
|
+
return @column_names_with_alias
|
1353
|
+
end
|
1354
|
+
|
1355
|
+
def extract_record(row)
|
1356
|
+
column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record}
|
1357
|
+
end
|
1358
|
+
|
1359
|
+
def record_id(row)
|
1360
|
+
row[aliased_primary_key]
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
def instantiate(row)
|
1364
|
+
@cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
|
1365
|
+
end
|
1366
|
+
end
|
1367
|
+
|
1368
|
+
class JoinAssociation < JoinBase
|
1369
|
+
attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
|
1370
|
+
delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
|
1371
|
+
|
1372
|
+
def initialize(reflection, join_dependency, parent = nil)
|
1373
|
+
reflection.check_validity!
|
1374
|
+
if reflection.options[:polymorphic]
|
1375
|
+
raise EagerLoadPolymorphicError.new(reflection)
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
super(reflection.klass)
|
1379
|
+
@parent = parent
|
1380
|
+
@reflection = reflection
|
1381
|
+
@aliased_prefix = "t#{ join_dependency.joins.size }"
|
1382
|
+
@aliased_table_name = table_name # start with the table name
|
1383
|
+
@parent_table_name = parent.active_record.table_name
|
1384
|
+
|
1385
|
+
if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{aliased_table_name.downcase}\son}
|
1386
|
+
join_dependency.table_aliases[aliased_table_name] += 1
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
unless join_dependency.table_aliases[aliased_table_name].zero?
|
1390
|
+
# if the table name has been used, then use an alias
|
1391
|
+
@aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}"
|
1392
|
+
table_index = join_dependency.table_aliases[aliased_table_name]
|
1393
|
+
@aliased_table_name = @aliased_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
|
1394
|
+
end
|
1395
|
+
join_dependency.table_aliases[aliased_table_name] += 1
|
1396
|
+
|
1397
|
+
if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through])
|
1398
|
+
@aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name
|
1399
|
+
unless join_dependency.table_aliases[aliased_join_table_name].zero?
|
1400
|
+
@aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join"
|
1401
|
+
table_index = join_dependency.table_aliases[aliased_join_table_name]
|
1402
|
+
@aliased_join_table_name = @aliased_join_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
|
1403
|
+
end
|
1404
|
+
join_dependency.table_aliases[aliased_join_table_name] += 1
|
1405
|
+
end
|
1406
|
+
end
|
1407
|
+
|
1408
|
+
def association_join
|
1409
|
+
join = case reflection.macro
|
1410
|
+
when :has_and_belongs_to_many
|
1411
|
+
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
|
1412
|
+
table_alias_for(options[:join_table], aliased_join_table_name),
|
1413
|
+
aliased_join_table_name,
|
1414
|
+
options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
|
1415
|
+
reflection.active_record.table_name, reflection.active_record.primary_key] +
|
1416
|
+
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
|
1417
|
+
table_name_and_alias, aliased_table_name, klass.primary_key,
|
1418
|
+
aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key
|
1419
|
+
]
|
1420
|
+
when :has_many, :has_one
|
1421
|
+
case
|
1422
|
+
when reflection.macro == :has_many && reflection.options[:through]
|
1423
|
+
through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
|
1424
|
+
if through_reflection.options[:as] # has_many :through against a polymorphic join
|
1425
|
+
polymorphic_foreign_key = through_reflection.options[:as].to_s + '_id'
|
1426
|
+
polymorphic_foreign_type = through_reflection.options[:as].to_s + '_type'
|
1427
|
+
|
1428
|
+
" LEFT OUTER JOIN %s ON (%s.%s = %s.%s AND %s.%s = %s) " % [
|
1429
|
+
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
|
1430
|
+
aliased_join_table_name, polymorphic_foreign_key,
|
1431
|
+
parent.aliased_table_name, parent.primary_key,
|
1432
|
+
aliased_join_table_name, polymorphic_foreign_type, klass.quote(parent.active_record.base_class.name)] +
|
1433
|
+
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias,
|
1434
|
+
aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key
|
1435
|
+
]
|
1436
|
+
else
|
1437
|
+
case source_reflection.macro
|
1438
|
+
when :belongs_to
|
1439
|
+
first_key = primary_key
|
1440
|
+
second_key = options[:foreign_key] || klass.to_s.classify.foreign_key
|
1441
|
+
when :has_many
|
1442
|
+
first_key = through_reflection.klass.to_s.classify.foreign_key
|
1443
|
+
second_key = options[:foreign_key] || primary_key
|
1444
|
+
end
|
1445
|
+
|
1446
|
+
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
|
1447
|
+
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
|
1448
|
+
through_reflection.primary_key_name,
|
1449
|
+
parent.aliased_table_name, parent.primary_key] +
|
1450
|
+
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
|
1451
|
+
table_name_and_alias,
|
1452
|
+
aliased_table_name, first_key,
|
1453
|
+
aliased_join_table_name, second_key
|
1454
|
+
]
|
1455
|
+
end
|
1456
|
+
|
1457
|
+
when reflection.macro == :has_many && reflection.options[:as]
|
1458
|
+
" LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [
|
1459
|
+
table_name_and_alias,
|
1460
|
+
aliased_table_name, "#{reflection.options[:as]}_id",
|
1461
|
+
parent.aliased_table_name, parent.primary_key,
|
1462
|
+
aliased_table_name, "#{reflection.options[:as]}_type",
|
1463
|
+
klass.quote(parent.active_record.base_class.name)
|
1464
|
+
]
|
1465
|
+
|
1466
|
+
else
|
1467
|
+
foreign_key = options[:foreign_key] || case reflection.macro
|
1468
|
+
when :has_many then reflection.active_record.to_s.classify
|
1469
|
+
when :has_one then reflection.active_record.to_s
|
1470
|
+
end.foreign_key
|
1471
|
+
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
|
1472
|
+
table_name_and_alias,
|
1473
|
+
aliased_table_name, foreign_key,
|
1474
|
+
parent.aliased_table_name, parent.primary_key
|
1475
|
+
]
|
1476
|
+
end
|
1477
|
+
when :belongs_to
|
1478
|
+
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
|
1479
|
+
table_name_and_alias, aliased_table_name, reflection.klass.primary_key,
|
1480
|
+
parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key
|
1481
|
+
]
|
1482
|
+
else
|
1483
|
+
""
|
1484
|
+
end || ''
|
1485
|
+
join << %(AND %s.%s = %s ) % [
|
1486
|
+
aliased_table_name,
|
1487
|
+
reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column),
|
1488
|
+
klass.quote(klass.name)] unless klass.descends_from_active_record?
|
1489
|
+
join << "AND #{interpolate_sql(sanitize_sql(reflection.options[:conditions]))} " if reflection.options[:conditions]
|
1490
|
+
join
|
1491
|
+
end
|
1492
|
+
|
1493
|
+
protected
|
1494
|
+
def pluralize(table_name)
|
1495
|
+
ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
|
1496
|
+
end
|
1497
|
+
|
1498
|
+
def table_alias_for(table_name, table_alias)
|
1499
|
+
"#{table_name} #{table_alias if table_name != table_alias}".strip
|
1500
|
+
end
|
1501
|
+
|
1502
|
+
def table_name_and_alias
|
1503
|
+
table_alias_for table_name, @aliased_table_name
|
1504
|
+
end
|
1505
|
+
|
1506
|
+
def interpolate_sql(sql)
|
1507
|
+
instance_eval("%@#{sql.gsub('@', '\@')}@")
|
1508
|
+
end
|
1509
|
+
end
|
1510
|
+
end
|
1065
1511
|
end
|
1066
1512
|
end
|
1067
1513
|
end
|