activerecord 2.2.3 → 2.3.2
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 +438 -396
- data/Rakefile +4 -2
- data/lib/active_record.rb +46 -43
- data/lib/active_record/association_preload.rb +34 -19
- data/lib/active_record/associations.rb +193 -251
- data/lib/active_record/associations/association_collection.rb +38 -21
- data/lib/active_record/associations/association_proxy.rb +11 -4
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +2 -2
- data/lib/active_record/associations/has_many_association.rb +2 -2
- data/lib/active_record/associations/has_many_through_association.rb +8 -8
- data/lib/active_record/associations/has_one_association.rb +11 -2
- data/lib/active_record/attribute_methods.rb +1 -0
- data/lib/active_record/autosave_association.rb +349 -0
- data/lib/active_record/base.rb +292 -106
- data/lib/active_record/batches.rb +73 -0
- data/lib/active_record/calculations.rb +34 -16
- data/lib/active_record/callbacks.rb +37 -8
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +16 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +103 -15
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +28 -25
- data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -5
- data/lib/active_record/connection_adapters/mysql_adapter.rb +50 -21
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -41
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +41 -21
- data/lib/active_record/dirty.rb +1 -1
- data/lib/active_record/dynamic_scope_match.rb +25 -0
- data/lib/active_record/fixtures.rb +193 -198
- data/lib/active_record/locale/en.yml +1 -1
- data/lib/active_record/locking/optimistic.rb +33 -0
- data/lib/active_record/migration.rb +8 -2
- data/lib/active_record/named_scope.rb +13 -6
- data/lib/active_record/nested_attributes.rb +329 -0
- data/lib/active_record/query_cache.rb +25 -13
- data/lib/active_record/reflection.rb +6 -1
- data/lib/active_record/schema_dumper.rb +2 -0
- data/lib/active_record/serialization.rb +3 -1
- data/lib/active_record/serializers/json_serializer.rb +19 -0
- data/lib/active_record/serializers/xml_serializer.rb +28 -13
- data/lib/active_record/session_store.rb +318 -0
- data/lib/active_record/test_case.rb +15 -9
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/transactions.rb +58 -8
- data/lib/active_record/validations.rb +29 -24
- data/lib/active_record/version.rb +2 -2
- data/test/cases/ar_schema_test.rb +0 -1
- data/test/cases/associations/belongs_to_associations_test.rb +35 -131
- data/test/cases/associations/cascaded_eager_loading_test.rb +8 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +29 -0
- data/test/cases/associations/eager_test.rb +137 -7
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +45 -7
- data/test/cases/associations/has_many_associations_test.rb +110 -149
- data/test/cases/associations/has_many_through_associations_test.rb +39 -7
- data/test/cases/associations/has_one_associations_test.rb +39 -92
- data/test/cases/associations/has_one_through_associations_test.rb +34 -3
- data/test/cases/associations/inner_join_association_test.rb +0 -5
- data/test/cases/associations/join_model_test.rb +5 -7
- data/test/cases/attribute_methods_test.rb +13 -1
- data/test/cases/autosave_association_test.rb +901 -0
- data/test/cases/base_test.rb +41 -21
- data/test/cases/batches_test.rb +61 -0
- data/test/cases/calculations_test.rb +37 -17
- data/test/cases/callbacks_test.rb +43 -5
- data/test/cases/connection_pool_test.rb +25 -0
- data/test/cases/copy_table_test_sqlite.rb +11 -0
- data/test/cases/datatype_test_postgresql.rb +1 -0
- data/test/cases/defaults_test.rb +37 -26
- data/test/cases/dirty_test.rb +26 -2
- data/test/cases/finder_test.rb +79 -44
- data/test/cases/fixtures_test.rb +15 -19
- data/test/cases/helper.rb +26 -19
- data/test/cases/inheritance_test.rb +2 -2
- data/test/cases/json_serialization_test.rb +1 -1
- data/test/cases/locking_test.rb +23 -5
- data/test/cases/method_scoping_test.rb +126 -3
- data/test/cases/migration_test.rb +253 -237
- data/test/cases/named_scope_test.rb +73 -3
- data/test/cases/nested_attributes_test.rb +509 -0
- data/test/cases/query_cache_test.rb +0 -4
- data/test/cases/reflection_test.rb +13 -3
- data/test/cases/reload_models_test.rb +3 -1
- data/test/cases/repair_helper.rb +50 -0
- data/test/cases/schema_dumper_test.rb +0 -1
- data/test/cases/transactions_test.rb +177 -12
- data/test/cases/validations_i18n_test.rb +288 -294
- data/test/cases/validations_test.rb +230 -180
- data/test/cases/xml_serialization_test.rb +19 -1
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/fixtures/member_types.yml +6 -0
- data/test/fixtures/members.yml +3 -1
- data/test/fixtures/people.yml +10 -1
- data/test/fixtures/toys.yml +4 -0
- data/test/models/author.rb +1 -2
- data/test/models/bird.rb +3 -0
- data/test/models/category.rb +1 -0
- data/test/models/company.rb +3 -0
- data/test/models/developer.rb +12 -0
- data/test/models/event.rb +3 -0
- data/test/models/member.rb +1 -0
- data/test/models/member_detail.rb +1 -0
- data/test/models/member_type.rb +3 -0
- data/test/models/owner.rb +2 -1
- data/test/models/parrot.rb +2 -0
- data/test/models/person.rb +6 -0
- data/test/models/pet.rb +2 -1
- data/test/models/pirate.rb +55 -1
- data/test/models/post.rb +6 -0
- data/test/models/project.rb +1 -0
- data/test/models/reply.rb +6 -0
- data/test/models/ship.rb +8 -1
- data/test/models/ship_part.rb +5 -0
- data/test/models/topic.rb +13 -1
- data/test/models/toy.rb +4 -0
- data/test/schema/schema.rb +35 -2
- metadata +70 -9
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
data/Rakefile
CHANGED
@@ -30,7 +30,9 @@ desc 'Run mysql, sqlite, and postgresql tests by default'
|
|
30
30
|
task :default => :test
|
31
31
|
|
32
32
|
desc 'Run mysql, sqlite, and postgresql tests'
|
33
|
-
task :test =>
|
33
|
+
task :test => defined?(JRUBY_VERSION) ?
|
34
|
+
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
|
35
|
+
%w(test_mysql test_sqlite3 test_postgresql)
|
34
36
|
|
35
37
|
for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb )
|
36
38
|
Rake::TestTask.new("test_#{adapter}") { |t|
|
@@ -175,7 +177,7 @@ spec = Gem::Specification.new do |s|
|
|
175
177
|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
176
178
|
end
|
177
179
|
|
178
|
-
s.add_dependency('activesupport', '= 2.2
|
180
|
+
s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD)
|
179
181
|
|
180
182
|
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
|
181
183
|
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
|
data/lib/active_record.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2004-
|
2
|
+
# Copyright (c) 2004-2009 David Heinemeier Hansson
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -31,51 +31,54 @@ rescue LoadError
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
require 'active_record/callbacks'
|
40
|
-
require 'active_record/reflection'
|
41
|
-
require 'active_record/associations'
|
42
|
-
require 'active_record/association_preload'
|
43
|
-
require 'active_record/aggregations'
|
44
|
-
require 'active_record/transactions'
|
45
|
-
require 'active_record/timestamp'
|
46
|
-
require 'active_record/locking/optimistic'
|
47
|
-
require 'active_record/locking/pessimistic'
|
48
|
-
require 'active_record/migration'
|
49
|
-
require 'active_record/schema'
|
50
|
-
require 'active_record/calculations'
|
51
|
-
require 'active_record/serialization'
|
52
|
-
require 'active_record/attribute_methods'
|
53
|
-
require 'active_record/dirty'
|
54
|
-
require 'active_record/dynamic_finder_match'
|
34
|
+
module ActiveRecord
|
35
|
+
# TODO: Review explicit loads to see if they will automatically be handled by the initilizer.
|
36
|
+
def self.load_all!
|
37
|
+
[Base, DynamicFinderMatch, ConnectionAdapters::AbstractAdapter]
|
38
|
+
end
|
55
39
|
|
56
|
-
|
57
|
-
extend ActiveRecord::QueryCache
|
58
|
-
include ActiveRecord::Validations
|
59
|
-
include ActiveRecord::Locking::Optimistic
|
60
|
-
include ActiveRecord::Locking::Pessimistic
|
61
|
-
include ActiveRecord::AttributeMethods
|
62
|
-
include ActiveRecord::Dirty
|
63
|
-
include ActiveRecord::Callbacks
|
64
|
-
include ActiveRecord::Observing
|
65
|
-
include ActiveRecord::Timestamp
|
66
|
-
include ActiveRecord::Associations
|
67
|
-
include ActiveRecord::NamedScope
|
68
|
-
include ActiveRecord::AssociationPreload
|
69
|
-
include ActiveRecord::Aggregations
|
70
|
-
include ActiveRecord::Transactions
|
71
|
-
include ActiveRecord::Reflection
|
72
|
-
include ActiveRecord::Calculations
|
73
|
-
include ActiveRecord::Serialization
|
74
|
-
end
|
40
|
+
autoload :VERSION, 'active_record/version'
|
75
41
|
|
76
|
-
|
42
|
+
autoload :ActiveRecordError, 'active_record/base'
|
43
|
+
autoload :ConnectionNotEstablished, 'active_record/base'
|
77
44
|
|
78
|
-
|
45
|
+
autoload :Aggregations, 'active_record/aggregations'
|
46
|
+
autoload :AssociationPreload, 'active_record/association_preload'
|
47
|
+
autoload :Associations, 'active_record/associations'
|
48
|
+
autoload :AttributeMethods, 'active_record/attribute_methods'
|
49
|
+
autoload :AutosaveAssociation, 'active_record/autosave_association'
|
50
|
+
autoload :Base, 'active_record/base'
|
51
|
+
autoload :Batches, 'active_record/batches'
|
52
|
+
autoload :Calculations, 'active_record/calculations'
|
53
|
+
autoload :Callbacks, 'active_record/callbacks'
|
54
|
+
autoload :Dirty, 'active_record/dirty'
|
55
|
+
autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match'
|
56
|
+
autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match'
|
57
|
+
autoload :Migration, 'active_record/migration'
|
58
|
+
autoload :Migrator, 'active_record/migration'
|
59
|
+
autoload :NamedScope, 'active_record/named_scope'
|
60
|
+
autoload :NestedAttributes, 'active_record/nested_attributes'
|
61
|
+
autoload :Observing, 'active_record/observer'
|
62
|
+
autoload :QueryCache, 'active_record/query_cache'
|
63
|
+
autoload :Reflection, 'active_record/reflection'
|
64
|
+
autoload :Schema, 'active_record/schema'
|
65
|
+
autoload :SchemaDumper, 'active_record/schema_dumper'
|
66
|
+
autoload :Serialization, 'active_record/serialization'
|
67
|
+
autoload :SessionStore, 'active_record/session_store'
|
68
|
+
autoload :TestCase, 'active_record/test_case'
|
69
|
+
autoload :Timestamp, 'active_record/timestamp'
|
70
|
+
autoload :Transactions, 'active_record/transactions'
|
71
|
+
autoload :Validations, 'active_record/validations'
|
72
|
+
|
73
|
+
module Locking
|
74
|
+
autoload :Optimistic, 'active_record/locking/optimistic'
|
75
|
+
autoload :Pessimistic, 'active_record/locking/pessimistic'
|
76
|
+
end
|
77
|
+
|
78
|
+
module ConnectionAdapters
|
79
|
+
autoload :AbstractAdapter, 'active_record/connection_adapters/abstract_adapter'
|
80
|
+
end
|
81
|
+
end
|
79
82
|
|
80
83
|
require 'active_record/i18n_interpolation_deprecation'
|
81
84
|
I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
|
@@ -43,7 +43,7 @@ module ActiveRecord
|
|
43
43
|
# loading in a more high-level (application developer-friendly) manner.
|
44
44
|
module ClassMethods
|
45
45
|
protected
|
46
|
-
|
46
|
+
|
47
47
|
# Eager loads the named associations for the given ActiveRecord record(s).
|
48
48
|
#
|
49
49
|
# In this description, 'association name' shall refer to the name passed
|
@@ -94,8 +94,8 @@ module ActiveRecord
|
|
94
94
|
raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol)
|
95
95
|
preload_associations(records, parent, preload_options)
|
96
96
|
reflection = reflections[parent]
|
97
|
-
parents = records.map {|record| record.send(reflection.name)}.flatten
|
98
|
-
unless parents.empty?
|
97
|
+
parents = records.map {|record| record.send(reflection.name)}.flatten.compact
|
98
|
+
unless parents.empty?
|
99
99
|
parents.first.class.preload_associations(parents, child)
|
100
100
|
end
|
101
101
|
end
|
@@ -113,7 +113,7 @@ module ActiveRecord
|
|
113
113
|
# unnecessarily
|
114
114
|
records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
|
115
115
|
raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
|
116
|
-
|
116
|
+
|
117
117
|
# 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
|
118
118
|
# the following could call 'preload_belongs_to_association',
|
119
119
|
# 'preload_has_many_association', etc.
|
@@ -128,7 +128,7 @@ module ActiveRecord
|
|
128
128
|
association_proxy.target.push(*[associated_record].flatten)
|
129
129
|
end
|
130
130
|
end
|
131
|
-
|
131
|
+
|
132
132
|
def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
|
133
133
|
parent_records.each do |parent_record|
|
134
134
|
parent_record.send("set_#{reflection_name}_target", associated_record)
|
@@ -183,18 +183,19 @@ module ActiveRecord
|
|
183
183
|
conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
|
184
184
|
conditions << append_conditions(reflection, preload_options)
|
185
185
|
|
186
|
-
associated_records = reflection.klass.
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
186
|
+
associated_records = reflection.klass.with_exclusive_scope do
|
187
|
+
reflection.klass.find(:all, :conditions => [conditions, ids],
|
188
|
+
:include => options[:include],
|
189
|
+
:joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
|
190
|
+
:select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
|
191
|
+
:order => options[:order])
|
192
|
+
end
|
192
193
|
set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
|
193
194
|
end
|
194
195
|
|
195
196
|
def preload_has_one_association(records, reflection, preload_options={})
|
196
197
|
return if records.first.send("loaded_#{reflection.name}?")
|
197
|
-
id_to_record_map, ids = construct_id_map(records)
|
198
|
+
id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key])
|
198
199
|
options = reflection.options
|
199
200
|
records.each {|record| record.send("set_#{reflection.name}_target", nil)}
|
200
201
|
if options[:through]
|
@@ -204,9 +205,18 @@ module ActiveRecord
|
|
204
205
|
unless through_records.empty?
|
205
206
|
source = reflection.source_reflection.name
|
206
207
|
through_records.first.class.preload_associations(through_records, source)
|
207
|
-
|
208
|
-
|
209
|
-
|
208
|
+
if through_reflection.macro == :belongs_to
|
209
|
+
rev_id_to_record_map, rev_ids = construct_id_map(records, through_primary_key)
|
210
|
+
rev_primary_key = through_reflection.klass.primary_key
|
211
|
+
through_records.each do |through_record|
|
212
|
+
add_preloaded_record_to_collection(rev_id_to_record_map[through_record[rev_primary_key].to_s],
|
213
|
+
reflection.name, through_record.send(source))
|
214
|
+
end
|
215
|
+
else
|
216
|
+
through_records.each do |through_record|
|
217
|
+
add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
|
218
|
+
reflection.name, through_record.send(source))
|
219
|
+
end
|
210
220
|
end
|
211
221
|
end
|
212
222
|
else
|
@@ -219,7 +229,7 @@ module ActiveRecord
|
|
219
229
|
options = reflection.options
|
220
230
|
|
221
231
|
primary_key_name = reflection.through_reflection_primary_key_name
|
222
|
-
id_to_record_map, ids = construct_id_map(records, primary_key_name)
|
232
|
+
id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
|
223
233
|
records.each {|record| record.send(reflection.name).loaded}
|
224
234
|
|
225
235
|
if options[:through]
|
@@ -239,7 +249,7 @@ module ActiveRecord
|
|
239
249
|
reflection.primary_key_name)
|
240
250
|
end
|
241
251
|
end
|
242
|
-
|
252
|
+
|
243
253
|
def preload_through_records(records, reflection, through_association)
|
244
254
|
through_reflection = reflections[through_association]
|
245
255
|
through_primary_key = through_reflection.primary_key_name
|
@@ -307,6 +317,7 @@ module ActiveRecord
|
|
307
317
|
|
308
318
|
klasses_and_ids.each do |klass_and_id|
|
309
319
|
klass_name, id_map = *klass_and_id
|
320
|
+
next if id_map.empty?
|
310
321
|
klass = klass_name.constantize
|
311
322
|
|
312
323
|
table_name = klass.quoted_table_name
|
@@ -323,11 +334,13 @@ module ActiveRecord
|
|
323
334
|
end
|
324
335
|
conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
|
325
336
|
conditions << append_conditions(reflection, preload_options)
|
326
|
-
associated_records = klass.
|
337
|
+
associated_records = klass.with_exclusive_scope do
|
338
|
+
klass.find(:all, :conditions => [conditions, ids],
|
327
339
|
:include => options[:include],
|
328
340
|
:select => options[:select],
|
329
341
|
:joins => options[:joins],
|
330
342
|
:order => options[:order])
|
343
|
+
end
|
331
344
|
set_association_single_records(id_map, reflection.name, associated_records, primary_key)
|
332
345
|
end
|
333
346
|
end
|
@@ -345,13 +358,15 @@ module ActiveRecord
|
|
345
358
|
|
346
359
|
conditions << append_conditions(reflection, preload_options)
|
347
360
|
|
348
|
-
reflection.klass.
|
361
|
+
reflection.klass.with_exclusive_scope do
|
362
|
+
reflection.klass.find(:all,
|
349
363
|
:select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
|
350
364
|
:include => preload_options[:include] || options[:include],
|
351
365
|
:conditions => [conditions, ids],
|
352
366
|
:joins => options[:joins],
|
353
367
|
:group => preload_options[:group] || options[:group],
|
354
368
|
:order => preload_options[:order] || options[:order])
|
369
|
+
end
|
355
370
|
end
|
356
371
|
|
357
372
|
|
@@ -1,13 +1,3 @@
|
|
1
|
-
require 'active_record/associations/association_proxy'
|
2
|
-
require 'active_record/associations/association_collection'
|
3
|
-
require 'active_record/associations/belongs_to_association'
|
4
|
-
require 'active_record/associations/belongs_to_polymorphic_association'
|
5
|
-
require 'active_record/associations/has_one_association'
|
6
|
-
require 'active_record/associations/has_many_association'
|
7
|
-
require 'active_record/associations/has_many_through_association'
|
8
|
-
require 'active_record/associations/has_and_belongs_to_many_association'
|
9
|
-
require 'active_record/associations/has_one_through_association'
|
10
|
-
|
11
1
|
module ActiveRecord
|
12
2
|
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
|
13
3
|
def initialize(owner_class_name, reflection)
|
@@ -32,7 +22,7 @@ module ActiveRecord
|
|
32
22
|
through_reflection = reflection.through_reflection
|
33
23
|
source_reflection_names = reflection.source_reflection_names
|
34
24
|
source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
|
35
|
-
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :
|
25
|
+
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} 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(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
|
36
26
|
end
|
37
27
|
end
|
38
28
|
|
@@ -61,6 +51,12 @@ module ActiveRecord
|
|
61
51
|
end
|
62
52
|
end
|
63
53
|
|
54
|
+
class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc:
|
55
|
+
def initialize(reflection)
|
56
|
+
super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
64
60
|
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
|
65
61
|
def initialize(reflection)
|
66
62
|
super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
|
@@ -75,6 +71,18 @@ module ActiveRecord
|
|
75
71
|
|
76
72
|
# See ActiveRecord::Associations::ClassMethods for documentation.
|
77
73
|
module Associations # :nodoc:
|
74
|
+
# These classes will be loaded when associations are created.
|
75
|
+
# So there is no need to eager load them.
|
76
|
+
autoload :AssociationCollection, 'active_record/associations/association_collection'
|
77
|
+
autoload :AssociationProxy, 'active_record/associations/association_proxy'
|
78
|
+
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
|
79
|
+
autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
|
80
|
+
autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
|
81
|
+
autoload :HasManyAssociation, 'active_record/associations/has_many_association'
|
82
|
+
autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
|
83
|
+
autoload :HasOneAssociation, 'active_record/associations/has_one_association'
|
84
|
+
autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
|
85
|
+
|
78
86
|
def self.included(base)
|
79
87
|
base.extend(ClassMethods)
|
80
88
|
end
|
@@ -86,6 +94,18 @@ module ActiveRecord
|
|
86
94
|
end unless self.new_record?
|
87
95
|
end
|
88
96
|
|
97
|
+
private
|
98
|
+
# Gets the specified association instance if it responds to :loaded?, nil otherwise.
|
99
|
+
def association_instance_get(name)
|
100
|
+
association = instance_variable_get("@#{name}")
|
101
|
+
association if association.respond_to?(:loaded?)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Set the specified association instance.
|
105
|
+
def association_instance_set(name, association)
|
106
|
+
instance_variable_set("@#{name}", association)
|
107
|
+
end
|
108
|
+
|
89
109
|
# Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
|
90
110
|
# "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
|
91
111
|
# specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own <tt>attr*</tt>
|
@@ -119,41 +139,40 @@ module ActiveRecord
|
|
119
139
|
# | | belongs_to |
|
120
140
|
# generated methods | belongs_to | :polymorphic | has_one
|
121
141
|
# ----------------------------------+------------+--------------+---------
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
# #other.nil? | X | X |
|
142
|
+
# other | X | X | X
|
143
|
+
# other=(other) | X | X | X
|
144
|
+
# build_other(attributes={}) | X | | X
|
145
|
+
# create_other(attributes={}) | X | | X
|
146
|
+
# other.create!(attributes={}) | | | X
|
128
147
|
#
|
129
148
|
# ===Collection associations (one-to-many / many-to-many)
|
130
149
|
# | | | has_many
|
131
150
|
# generated methods | habtm | has_many | :through
|
132
151
|
# ----------------------------------+-------+----------+----------
|
133
|
-
#
|
134
|
-
#
|
135
|
-
#
|
136
|
-
#
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
#
|
152
|
+
# others | X | X | X
|
153
|
+
# others=(other,other,...) | X | X | X
|
154
|
+
# other_ids | X | X | X
|
155
|
+
# other_ids=(id,id,...) | X | X | X
|
156
|
+
# others<< | X | X | X
|
157
|
+
# others.push | X | X | X
|
158
|
+
# others.concat | X | X | X
|
159
|
+
# others.build(attributes={}) | X | X | X
|
160
|
+
# others.create(attributes={}) | X | X | X
|
161
|
+
# others.create!(attributes={}) | X | X | X
|
162
|
+
# others.size | X | X | X
|
163
|
+
# others.length | X | X | X
|
164
|
+
# others.count | X | X | X
|
165
|
+
# others.sum(args*,&block) | X | X | X
|
166
|
+
# others.empty? | X | X | X
|
167
|
+
# others.clear | X | X | X
|
168
|
+
# others.delete(other,other,...) | X | X | X
|
169
|
+
# others.delete_all | X | X |
|
170
|
+
# others.destroy_all | X | X | X
|
171
|
+
# others.find(*args) | X | X | X
|
172
|
+
# others.find_first | X | |
|
173
|
+
# others.exists? | X | X | X
|
174
|
+
# others.uniq | X | X | X
|
175
|
+
# others.reset | X | X | X
|
157
176
|
#
|
158
177
|
# == Cardinality and associations
|
159
178
|
#
|
@@ -254,6 +273,10 @@ module ActiveRecord
|
|
254
273
|
# You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
|
255
274
|
# aware of, mostly involving the saving of associated objects.
|
256
275
|
#
|
276
|
+
# Unless you enable the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
|
277
|
+
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association,
|
278
|
+
# in which case the members are always saved.
|
279
|
+
#
|
257
280
|
# === One-to-one associations
|
258
281
|
#
|
259
282
|
# * Assigning an object to a +has_one+ association automatically saves that object and the object being replaced (if there is one), in
|
@@ -650,7 +673,7 @@ module ActiveRecord
|
|
650
673
|
# Returns the number of associated objects.
|
651
674
|
# [collection.find(...)]
|
652
675
|
# Finds an associated object according to the same rules as ActiveRecord::Base.find.
|
653
|
-
# [collection.
|
676
|
+
# [collection.exists?(...)]
|
654
677
|
# Checks whether an associated object with the given conditions exists.
|
655
678
|
# Uses the same rules as ActiveRecord::Base.exists?.
|
656
679
|
# [collection.build(attributes = {}, ...)]
|
@@ -680,7 +703,7 @@ module ActiveRecord
|
|
680
703
|
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
|
681
704
|
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
|
682
705
|
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
|
683
|
-
# * <tt>Firm#clients.
|
706
|
+
# * <tt>Firm#clients.exists?(:name => 'ACME')</tt> (similar to <tt>Client.exists?(:name => 'ACME', :firm_id => firm.id)</tt>)
|
684
707
|
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
|
685
708
|
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
|
686
709
|
# The declaration can also include an options hash to specialize the behavior of the association.
|
@@ -722,6 +745,8 @@ module ActiveRecord
|
|
722
745
|
# Specify second-order associations that should be eager loaded when the collection is loaded.
|
723
746
|
# [:group]
|
724
747
|
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
748
|
+
# [:having]
|
749
|
+
# Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
|
725
750
|
# [:limit]
|
726
751
|
# An integer determining the limit on the number of rows that should be returned.
|
727
752
|
# [:offset]
|
@@ -748,6 +773,9 @@ module ActiveRecord
|
|
748
773
|
# If true, all the associated objects are readonly through the association.
|
749
774
|
# [:validate]
|
750
775
|
# If false, don't validate the associated objects when saving the parent object. true by default.
|
776
|
+
# [:autosave]
|
777
|
+
# If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
|
778
|
+
#
|
751
779
|
# Option examples:
|
752
780
|
# has_many :comments, :order => "posted_on"
|
753
781
|
# has_many :comments, :include => :author
|
@@ -764,11 +792,7 @@ module ActiveRecord
|
|
764
792
|
# 'ORDER BY p.first_name'
|
765
793
|
def has_many(association_id, options = {}, &extension)
|
766
794
|
reflection = create_has_many_reflection(association_id, options, &extension)
|
767
|
-
|
768
795
|
configure_dependency_for_has_many(reflection)
|
769
|
-
|
770
|
-
add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
|
771
|
-
add_multiple_associated_save_callbacks(reflection.name)
|
772
796
|
add_association_callbacks(reflection.name, reflection.options)
|
773
797
|
|
774
798
|
if options[:through]
|
@@ -790,8 +814,6 @@ module ActiveRecord
|
|
790
814
|
# [association=(associate)]
|
791
815
|
# Assigns the associate object, extracts the primary key, sets it as the foreign key,
|
792
816
|
# and saves the associate object.
|
793
|
-
# [association.nil?]
|
794
|
-
# Returns +true+ if there is no associated object.
|
795
817
|
# [build_association(attributes = {})]
|
796
818
|
# Returns a new object of the associated type that has been instantiated
|
797
819
|
# with +attributes+ and linked to this object through a foreign key, but has not
|
@@ -810,7 +832,6 @@ module ActiveRecord
|
|
810
832
|
# An Account class declares <tt>has_one :beneficiary</tt>, which will add:
|
811
833
|
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
|
812
834
|
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
|
813
|
-
# * <tt>Account#beneficiary.nil?</tt>
|
814
835
|
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
|
815
836
|
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
|
816
837
|
#
|
@@ -853,14 +874,16 @@ module ActiveRecord
|
|
853
874
|
# [:source]
|
854
875
|
# Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
|
855
876
|
# inferred from the association. <tt>has_one :favorite, :through => :favorites</tt> will look for a
|
856
|
-
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
|
877
|
+
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
|
857
878
|
# [:source_type]
|
858
879
|
# Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
|
859
|
-
# association is a polymorphic +belongs_to+.
|
880
|
+
# association is a polymorphic +belongs_to+.
|
860
881
|
# [:readonly]
|
861
882
|
# If true, the associated object is readonly through the association.
|
862
883
|
# [:validate]
|
863
884
|
# If false, don't validate the associated object when saving the parent object. +false+ by default.
|
885
|
+
# [:autosave]
|
886
|
+
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
|
864
887
|
#
|
865
888
|
# Option examples:
|
866
889
|
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
|
@@ -877,25 +900,9 @@ module ActiveRecord
|
|
877
900
|
association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation)
|
878
901
|
else
|
879
902
|
reflection = create_has_one_reflection(association_id, options)
|
880
|
-
|
881
|
-
ivar = "@#{reflection.name}"
|
882
|
-
|
883
|
-
method_name = "has_one_after_save_for_#{reflection.name}".to_sym
|
884
|
-
define_method(method_name) do
|
885
|
-
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
886
|
-
|
887
|
-
if !association.nil? && (new_record? || association.new_record? || association[reflection.primary_key_name] != id)
|
888
|
-
association[reflection.primary_key_name] = id
|
889
|
-
association.save(true)
|
890
|
-
end
|
891
|
-
end
|
892
|
-
after_save method_name
|
893
|
-
|
894
|
-
add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
|
895
903
|
association_accessor_methods(reflection, HasOneAssociation)
|
896
904
|
association_constructor_method(:build, reflection, HasOneAssociation)
|
897
905
|
association_constructor_method(:create, reflection, HasOneAssociation)
|
898
|
-
|
899
906
|
configure_dependency_for_has_one(reflection)
|
900
907
|
end
|
901
908
|
end
|
@@ -912,8 +919,6 @@ module ActiveRecord
|
|
912
919
|
# Returns the associated object. +nil+ is returned if none is found.
|
913
920
|
# [association=(associate)]
|
914
921
|
# Assigns the associate object, extracts the primary key, and sets it as the foreign key.
|
915
|
-
# [association.nil?]
|
916
|
-
# Returns +true+ if there is no associated object.
|
917
922
|
# [build_association(attributes = {})]
|
918
923
|
# Returns a new object of the associated type that has been instantiated
|
919
924
|
# with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
|
@@ -931,7 +936,6 @@ module ActiveRecord
|
|
931
936
|
# * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
|
932
937
|
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
|
933
938
|
# * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
|
934
|
-
# * <tt>Post#author.nil?</tt>
|
935
939
|
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
|
936
940
|
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
|
937
941
|
# The declaration can also include an options hash to specialize the behavior of the association.
|
@@ -975,6 +979,8 @@ module ActiveRecord
|
|
975
979
|
# If true, the associated object is readonly through the association.
|
976
980
|
# [:validate]
|
977
981
|
# If false, don't validate the associated objects when saving the parent object. +false+ by default.
|
982
|
+
# [:autosave]
|
983
|
+
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
|
978
984
|
#
|
979
985
|
# Option examples:
|
980
986
|
# belongs_to :firm, :foreign_key => "client_of"
|
@@ -987,54 +993,17 @@ module ActiveRecord
|
|
987
993
|
def belongs_to(association_id, options = {})
|
988
994
|
reflection = create_belongs_to_reflection(association_id, options)
|
989
995
|
|
990
|
-
ivar = "@#{reflection.name}"
|
991
|
-
|
992
996
|
if reflection.options[:polymorphic]
|
993
997
|
association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
|
994
|
-
|
995
|
-
method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
|
996
|
-
define_method(method_name) do
|
997
|
-
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
998
|
-
|
999
|
-
if association && association.target
|
1000
|
-
if association.new_record?
|
1001
|
-
association.save(true)
|
1002
|
-
end
|
1003
|
-
|
1004
|
-
if association.updated?
|
1005
|
-
self[reflection.primary_key_name] = association.id
|
1006
|
-
self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
|
1007
|
-
end
|
1008
|
-
end
|
1009
|
-
end
|
1010
|
-
before_save method_name
|
1011
998
|
else
|
1012
999
|
association_accessor_methods(reflection, BelongsToAssociation)
|
1013
1000
|
association_constructor_method(:build, reflection, BelongsToAssociation)
|
1014
1001
|
association_constructor_method(:create, reflection, BelongsToAssociation)
|
1015
|
-
|
1016
|
-
method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
|
1017
|
-
define_method(method_name) do
|
1018
|
-
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
1019
|
-
|
1020
|
-
if !association.nil?
|
1021
|
-
if association.new_record?
|
1022
|
-
association.save(true)
|
1023
|
-
end
|
1024
|
-
|
1025
|
-
if association.updated?
|
1026
|
-
self[reflection.primary_key_name] = association.id
|
1027
|
-
end
|
1028
|
-
end
|
1029
|
-
end
|
1030
|
-
before_save method_name
|
1031
1002
|
end
|
1032
1003
|
|
1033
1004
|
# Create the callbacks to update counter cache
|
1034
1005
|
if options[:counter_cache]
|
1035
|
-
cache_column =
|
1036
|
-
"#{self.to_s.demodulize.underscore.pluralize}_count" :
|
1037
|
-
options[:counter_cache]
|
1006
|
+
cache_column = reflection.counter_cache_column
|
1038
1007
|
|
1039
1008
|
method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
|
1040
1009
|
define_method(method_name) do
|
@@ -1055,8 +1024,6 @@ module ActiveRecord
|
|
1055
1024
|
)
|
1056
1025
|
end
|
1057
1026
|
|
1058
|
-
add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
|
1059
|
-
|
1060
1027
|
configure_dependency_for_belongs_to(reflection)
|
1061
1028
|
end
|
1062
1029
|
|
@@ -1071,6 +1038,22 @@ module ActiveRecord
|
|
1071
1038
|
# but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
|
1072
1039
|
# custom <tt>:join_table</tt> option if you need to.
|
1073
1040
|
#
|
1041
|
+
# The join table should not have a primary key or a model associated with it. You must manually generate the
|
1042
|
+
# join table with a migration such as this:
|
1043
|
+
#
|
1044
|
+
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
|
1045
|
+
# def self.up
|
1046
|
+
# create_table :developers_projects, :id => false do |t|
|
1047
|
+
# t.integer :developer_id
|
1048
|
+
# t.integer :project_id
|
1049
|
+
# end
|
1050
|
+
# end
|
1051
|
+
#
|
1052
|
+
# def self.down
|
1053
|
+
# drop_table :developers_projects
|
1054
|
+
# end
|
1055
|
+
# end
|
1056
|
+
#
|
1074
1057
|
# Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
|
1075
1058
|
# +has_and_belongs_to_many+ associations. Records returned from join tables with additional attributes will be marked as
|
1076
1059
|
# readonly (because we can't save changes to the additional attributes). It's strongly recommended that you upgrade any
|
@@ -1103,7 +1086,7 @@ module ActiveRecord
|
|
1103
1086
|
# Finds an associated object responding to the +id+ and that
|
1104
1087
|
# meets the condition that it has to be associated with this object.
|
1105
1088
|
# Uses the same rules as ActiveRecord::Base.find.
|
1106
|
-
# [collection.
|
1089
|
+
# [collection.exists?(...)]
|
1107
1090
|
# Checks whether an associated object with the given conditions exists.
|
1108
1091
|
# Uses the same rules as ActiveRecord::Base.exists?.
|
1109
1092
|
# [collection.build(attributes = {})]
|
@@ -1129,7 +1112,7 @@ module ActiveRecord
|
|
1129
1112
|
# * <tt>Developer#projects.empty?</tt>
|
1130
1113
|
# * <tt>Developer#projects.size</tt>
|
1131
1114
|
# * <tt>Developer#projects.find(id)</tt>
|
1132
|
-
# * <tt>Developer#clients.
|
1115
|
+
# * <tt>Developer#clients.exists?(...)</tt>
|
1133
1116
|
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>)
|
1134
1117
|
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>)
|
1135
1118
|
# The declaration may include an options hash to specialize the behavior of the association.
|
@@ -1147,11 +1130,12 @@ module ActiveRecord
|
|
1147
1130
|
# [:foreign_key]
|
1148
1131
|
# Specify the foreign key used for the association. By default this is guessed to be the name
|
1149
1132
|
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association
|
1150
|
-
# will use "person_id" as the default <tt>:foreign_key</tt>.
|
1133
|
+
# to Project will use "person_id" as the default <tt>:foreign_key</tt>.
|
1151
1134
|
# [:association_foreign_key]
|
1152
|
-
# Specify the
|
1153
|
-
# guessed to be the name of the associated class in lower-case and "_id" suffixed.
|
1154
|
-
#
|
1135
|
+
# Specify the foreign key used for the association on the receiving side of the association.
|
1136
|
+
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
|
1137
|
+
# So if a Person class makes a +has_and_belongs_to_many+ association to Project,
|
1138
|
+
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
|
1155
1139
|
# [:conditions]
|
1156
1140
|
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
|
1157
1141
|
# SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
|
@@ -1179,6 +1163,8 @@ module ActiveRecord
|
|
1179
1163
|
# Specify second-order associations that should be eager loaded when the collection is loaded.
|
1180
1164
|
# [:group]
|
1181
1165
|
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
1166
|
+
# [:having]
|
1167
|
+
# Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
|
1182
1168
|
# [:limit]
|
1183
1169
|
# An integer determining the limit on the number of rows that should be returned.
|
1184
1170
|
# [:offset]
|
@@ -1190,6 +1176,8 @@ module ActiveRecord
|
|
1190
1176
|
# If true, all the associated objects are readonly through the association.
|
1191
1177
|
# [:validate]
|
1192
1178
|
# If false, don't validate the associated objects when saving the parent object. +true+ by default.
|
1179
|
+
# [:autosave]
|
1180
|
+
# If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
|
1193
1181
|
#
|
1194
1182
|
# Option examples:
|
1195
1183
|
# has_and_belongs_to_many :projects
|
@@ -1201,20 +1189,17 @@ module ActiveRecord
|
|
1201
1189
|
# 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
|
1202
1190
|
def has_and_belongs_to_many(association_id, options = {}, &extension)
|
1203
1191
|
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
|
1204
|
-
|
1205
|
-
add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
|
1206
|
-
add_multiple_associated_save_callbacks(reflection.name)
|
1207
1192
|
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
|
1208
1193
|
|
1209
1194
|
# Don't use a before_destroy callback since users' before_destroy
|
1210
1195
|
# callbacks will be executed after the association is wiped out.
|
1211
1196
|
old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
|
1212
1197
|
class_eval <<-end_eval unless method_defined?(old_method)
|
1213
|
-
alias_method :#{old_method}, :destroy_without_callbacks
|
1214
|
-
def destroy_without_callbacks
|
1215
|
-
#{reflection.name}.clear
|
1216
|
-
#{old_method}
|
1217
|
-
end
|
1198
|
+
alias_method :#{old_method}, :destroy_without_callbacks # alias_method :destroy_without_habtm_shim_for_posts, :destroy_without_callbacks
|
1199
|
+
def destroy_without_callbacks # def destroy_without_callbacks
|
1200
|
+
#{reflection.name}.clear # posts.clear
|
1201
|
+
#{old_method} # destroy_without_habtm_shim_for_posts
|
1202
|
+
end # end
|
1218
1203
|
end_eval
|
1219
1204
|
|
1220
1205
|
add_association_callbacks(reflection.name, options)
|
@@ -1237,33 +1222,30 @@ module ActiveRecord
|
|
1237
1222
|
end
|
1238
1223
|
|
1239
1224
|
def association_accessor_methods(reflection, association_proxy_class)
|
1240
|
-
ivar = "@#{reflection.name}"
|
1241
|
-
|
1242
1225
|
define_method(reflection.name) do |*params|
|
1243
1226
|
force_reload = params.first unless params.empty?
|
1244
|
-
|
1245
|
-
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
1227
|
+
association = association_instance_get(reflection.name)
|
1246
1228
|
|
1247
1229
|
if association.nil? || force_reload
|
1248
1230
|
association = association_proxy_class.new(self, reflection)
|
1249
1231
|
retval = association.reload
|
1250
1232
|
if retval.nil? and association_proxy_class == BelongsToAssociation
|
1251
|
-
|
1233
|
+
association_instance_set(reflection.name, nil)
|
1252
1234
|
return nil
|
1253
1235
|
end
|
1254
|
-
|
1236
|
+
association_instance_set(reflection.name, association)
|
1255
1237
|
end
|
1256
1238
|
|
1257
1239
|
association.target.nil? ? nil : association
|
1258
1240
|
end
|
1259
1241
|
|
1260
1242
|
define_method("loaded_#{reflection.name}?") do
|
1261
|
-
association =
|
1243
|
+
association = association_instance_get(reflection.name)
|
1262
1244
|
association && association.loaded?
|
1263
1245
|
end
|
1264
1246
|
|
1265
1247
|
define_method("#{reflection.name}=") do |new_value|
|
1266
|
-
association =
|
1248
|
+
association = association_instance_get(reflection.name)
|
1267
1249
|
|
1268
1250
|
if association.nil? || association.target != new_value
|
1269
1251
|
association = association_proxy_class.new(self, reflection)
|
@@ -1274,7 +1256,7 @@ module ActiveRecord
|
|
1274
1256
|
self.send(reflection.name, new_value)
|
1275
1257
|
else
|
1276
1258
|
association.replace(new_value)
|
1277
|
-
|
1259
|
+
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
1278
1260
|
end
|
1279
1261
|
end
|
1280
1262
|
|
@@ -1282,20 +1264,18 @@ module ActiveRecord
|
|
1282
1264
|
return if target.nil? and association_proxy_class == BelongsToAssociation
|
1283
1265
|
association = association_proxy_class.new(self, reflection)
|
1284
1266
|
association.target = target
|
1285
|
-
|
1267
|
+
association_instance_set(reflection.name, association)
|
1286
1268
|
end
|
1287
1269
|
end
|
1288
1270
|
|
1289
1271
|
def collection_reader_method(reflection, association_proxy_class)
|
1290
1272
|
define_method(reflection.name) do |*params|
|
1291
|
-
ivar = "@#{reflection.name}"
|
1292
|
-
|
1293
1273
|
force_reload = params.first unless params.empty?
|
1294
|
-
association =
|
1274
|
+
association = association_instance_get(reflection.name)
|
1295
1275
|
|
1296
|
-
unless association
|
1276
|
+
unless association
|
1297
1277
|
association = association_proxy_class.new(self, reflection)
|
1298
|
-
|
1278
|
+
association_instance_set(reflection.name, association)
|
1299
1279
|
end
|
1300
1280
|
|
1301
1281
|
association.reload if force_reload
|
@@ -1330,86 +1310,15 @@ module ActiveRecord
|
|
1330
1310
|
end
|
1331
1311
|
end
|
1332
1312
|
|
1333
|
-
def add_single_associated_validation_callbacks(association_name)
|
1334
|
-
method_name = "validate_associated_records_for_#{association_name}".to_sym
|
1335
|
-
define_method(method_name) do
|
1336
|
-
association = instance_variable_get("@#{association_name}")
|
1337
|
-
if !association.nil?
|
1338
|
-
errors.add association_name unless association.target.nil? || association.valid?
|
1339
|
-
end
|
1340
|
-
end
|
1341
|
-
|
1342
|
-
validate method_name
|
1343
|
-
end
|
1344
|
-
|
1345
|
-
def add_multiple_associated_validation_callbacks(association_name)
|
1346
|
-
method_name = "validate_associated_records_for_#{association_name}".to_sym
|
1347
|
-
ivar = "@#{association_name}"
|
1348
|
-
|
1349
|
-
define_method(method_name) do
|
1350
|
-
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
1351
|
-
|
1352
|
-
if association.respond_to?(:loaded?)
|
1353
|
-
if new_record?
|
1354
|
-
association
|
1355
|
-
elsif association.loaded?
|
1356
|
-
association.select { |record| record.new_record? }
|
1357
|
-
else
|
1358
|
-
association.target.select { |record| record.new_record? }
|
1359
|
-
end.each do |record|
|
1360
|
-
errors.add association_name unless record.valid?
|
1361
|
-
end
|
1362
|
-
end
|
1363
|
-
end
|
1364
|
-
|
1365
|
-
validate method_name
|
1366
|
-
end
|
1367
|
-
|
1368
|
-
def add_multiple_associated_save_callbacks(association_name)
|
1369
|
-
ivar = "@#{association_name}"
|
1370
|
-
|
1371
|
-
method_name = "before_save_associated_records_for_#{association_name}".to_sym
|
1372
|
-
define_method(method_name) do
|
1373
|
-
@new_record_before_save = new_record?
|
1374
|
-
true
|
1375
|
-
end
|
1376
|
-
before_save method_name
|
1377
|
-
|
1378
|
-
method_name = "after_create_or_update_associated_records_for_#{association_name}".to_sym
|
1379
|
-
define_method(method_name) do
|
1380
|
-
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
1381
|
-
|
1382
|
-
records_to_save = if @new_record_before_save
|
1383
|
-
association
|
1384
|
-
elsif association.respond_to?(:loaded?) && association.loaded?
|
1385
|
-
association.select { |record| record.new_record? }
|
1386
|
-
elsif association.respond_to?(:loaded?) && !association.loaded?
|
1387
|
-
association.target.select { |record| record.new_record? }
|
1388
|
-
else
|
1389
|
-
[]
|
1390
|
-
end
|
1391
|
-
records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank?
|
1392
|
-
|
1393
|
-
# reconstruct the SQL queries now that we know the owner's id
|
1394
|
-
association.send(:construct_sql) if association.respond_to?(:construct_sql)
|
1395
|
-
end
|
1396
|
-
|
1397
|
-
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
1398
|
-
after_create method_name
|
1399
|
-
after_update method_name
|
1400
|
-
end
|
1401
|
-
|
1402
1313
|
def association_constructor_method(constructor, reflection, association_proxy_class)
|
1403
1314
|
define_method("#{constructor}_#{reflection.name}") do |*params|
|
1404
|
-
ivar = "@#{reflection.name}"
|
1405
|
-
|
1406
1315
|
attributees = params.first unless params.empty?
|
1407
1316
|
replace_existing = params[1].nil? ? true : params[1]
|
1408
|
-
association =
|
1317
|
+
association = association_instance_get(reflection.name)
|
1409
1318
|
|
1410
|
-
|
1319
|
+
unless association
|
1411
1320
|
association = association_proxy_class.new(self, reflection)
|
1412
|
-
|
1321
|
+
association_instance_set(reflection.name, association)
|
1413
1322
|
end
|
1414
1323
|
|
1415
1324
|
if association_proxy_class == HasOneAssociation
|
@@ -1447,7 +1356,7 @@ module ActiveRecord
|
|
1447
1356
|
dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions]
|
1448
1357
|
dependent_conditions << extra_conditions if extra_conditions
|
1449
1358
|
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
|
1450
|
-
|
1359
|
+
dependent_conditions = dependent_conditions.gsub('@', '\@')
|
1451
1360
|
case reflection.options[:dependent]
|
1452
1361
|
when :destroy
|
1453
1362
|
method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
|
@@ -1457,22 +1366,22 @@ module ActiveRecord
|
|
1457
1366
|
before_destroy method_name
|
1458
1367
|
when :delete_all
|
1459
1368
|
module_eval %Q{
|
1460
|
-
before_destroy do |record|
|
1461
|
-
delete_all_has_many_dependencies(record,
|
1462
|
-
"#{reflection.name}",
|
1463
|
-
#{reflection.class_name},
|
1464
|
-
|
1465
|
-
end
|
1369
|
+
before_destroy do |record| # before_destroy do |record|
|
1370
|
+
delete_all_has_many_dependencies(record, # delete_all_has_many_dependencies(record,
|
1371
|
+
"#{reflection.name}", # "posts",
|
1372
|
+
#{reflection.class_name}, # Post,
|
1373
|
+
%@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
|
1374
|
+
end # end
|
1466
1375
|
}
|
1467
1376
|
when :nullify
|
1468
1377
|
module_eval %Q{
|
1469
|
-
before_destroy do |record|
|
1470
|
-
nullify_has_many_dependencies(record,
|
1471
|
-
"#{reflection.name}",
|
1472
|
-
#{reflection.class_name},
|
1473
|
-
"#{reflection.primary_key_name}",
|
1474
|
-
|
1475
|
-
end
|
1378
|
+
before_destroy do |record| # before_destroy do |record|
|
1379
|
+
nullify_has_many_dependencies(record, # nullify_has_many_dependencies(record,
|
1380
|
+
"#{reflection.name}", # "posts",
|
1381
|
+
#{reflection.class_name}, # Post,
|
1382
|
+
"#{reflection.primary_key_name}", # "user_id",
|
1383
|
+
%@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
|
1384
|
+
end # end
|
1476
1385
|
}
|
1477
1386
|
else
|
1478
1387
|
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
|
@@ -1525,14 +1434,14 @@ module ActiveRecord
|
|
1525
1434
|
association = send(reflection.name)
|
1526
1435
|
association.destroy unless association.nil?
|
1527
1436
|
end
|
1528
|
-
|
1437
|
+
after_destroy method_name
|
1529
1438
|
when :delete
|
1530
1439
|
method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym
|
1531
1440
|
define_method(method_name) do
|
1532
1441
|
association = send(reflection.name)
|
1533
1442
|
association.delete unless association.nil?
|
1534
1443
|
end
|
1535
|
-
|
1444
|
+
after_destroy method_name
|
1536
1445
|
else
|
1537
1446
|
raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})"
|
1538
1447
|
end
|
@@ -1551,7 +1460,7 @@ module ActiveRecord
|
|
1551
1460
|
@@valid_keys_for_has_many_association = [
|
1552
1461
|
:class_name, :table_name, :foreign_key, :primary_key,
|
1553
1462
|
:dependent,
|
1554
|
-
:select, :conditions, :include, :order, :group, :limit, :offset,
|
1463
|
+
:select, :conditions, :include, :order, :group, :having, :limit, :offset,
|
1555
1464
|
:as, :through, :source, :source_type,
|
1556
1465
|
:uniq,
|
1557
1466
|
:finder_sql, :counter_sql,
|
@@ -1607,7 +1516,7 @@ module ActiveRecord
|
|
1607
1516
|
mattr_accessor :valid_keys_for_has_and_belongs_to_many_association
|
1608
1517
|
@@valid_keys_for_has_and_belongs_to_many_association = [
|
1609
1518
|
:class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
|
1610
|
-
:select, :conditions, :include, :order, :group, :limit, :offset,
|
1519
|
+
:select, :conditions, :include, :order, :group, :having, :limit, :offset,
|
1611
1520
|
:uniq,
|
1612
1521
|
:finder_sql, :counter_sql, :delete_sql, :insert_sql,
|
1613
1522
|
:before_add, :after_add, :before_remove, :after_remove,
|
@@ -1621,6 +1530,10 @@ module ActiveRecord
|
|
1621
1530
|
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
1622
1531
|
|
1623
1532
|
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
|
1533
|
+
|
1534
|
+
if reflection.association_foreign_key == reflection.primary_key_name
|
1535
|
+
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
|
1536
|
+
end
|
1624
1537
|
|
1625
1538
|
reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
|
1626
1539
|
|
@@ -1656,7 +1569,7 @@ module ActiveRecord
|
|
1656
1569
|
add_conditions!(sql, options[:conditions], scope)
|
1657
1570
|
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
1658
1571
|
|
1659
|
-
add_group!(sql, options[:group], scope)
|
1572
|
+
add_group!(sql, options[:group], options[:having], scope)
|
1660
1573
|
add_order!(sql, options[:order], scope)
|
1661
1574
|
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
1662
1575
|
add_lock!(sql, options, scope)
|
@@ -1712,7 +1625,7 @@ module ActiveRecord
|
|
1712
1625
|
end
|
1713
1626
|
|
1714
1627
|
add_conditions!(sql, options[:conditions], scope)
|
1715
|
-
add_group!(sql, options[:group], scope)
|
1628
|
+
add_group!(sql, options[:group], options[:having], scope)
|
1716
1629
|
|
1717
1630
|
if order && is_distinct
|
1718
1631
|
connection.add_order_by_for_association_limiting!(sql, :order => order)
|
@@ -1725,46 +1638,70 @@ module ActiveRecord
|
|
1725
1638
|
return sanitize_sql(sql)
|
1726
1639
|
end
|
1727
1640
|
|
1641
|
+
def tables_in_string(string)
|
1642
|
+
return [] if string.blank?
|
1643
|
+
string.scan(/([\.a-zA-Z_]+).?\./).flatten
|
1644
|
+
end
|
1645
|
+
|
1728
1646
|
def conditions_tables(options)
|
1729
1647
|
# look in both sets of conditions
|
1730
1648
|
conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
|
1731
1649
|
case cond
|
1732
1650
|
when nil then all
|
1733
1651
|
when Array then all << cond.first
|
1652
|
+
when Hash then all << cond.keys
|
1734
1653
|
else all << cond
|
1735
1654
|
end
|
1736
1655
|
end
|
1737
|
-
conditions.join(' ')
|
1656
|
+
tables_in_string(conditions.join(' '))
|
1738
1657
|
end
|
1739
1658
|
|
1740
1659
|
def order_tables(options)
|
1741
1660
|
order = [options[:order], scope(:find, :order) ].join(", ")
|
1742
1661
|
return [] unless order && order.is_a?(String)
|
1743
|
-
order
|
1662
|
+
tables_in_string(order)
|
1744
1663
|
end
|
1745
1664
|
|
1746
1665
|
def selects_tables(options)
|
1747
1666
|
select = options[:select]
|
1748
1667
|
return [] unless select && select.is_a?(String)
|
1749
|
-
select
|
1668
|
+
tables_in_string(select)
|
1669
|
+
end
|
1670
|
+
|
1671
|
+
def joined_tables(options)
|
1672
|
+
scope = scope(:find)
|
1673
|
+
joins = options[:joins]
|
1674
|
+
merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
|
1675
|
+
[table_name] + case merged_joins
|
1676
|
+
when Symbol, Hash, Array
|
1677
|
+
if array_of_strings?(merged_joins)
|
1678
|
+
tables_in_string(merged_joins.join(' '))
|
1679
|
+
else
|
1680
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
|
1681
|
+
join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact
|
1682
|
+
end
|
1683
|
+
else
|
1684
|
+
tables_in_string(merged_joins)
|
1685
|
+
end
|
1750
1686
|
end
|
1751
1687
|
|
1752
1688
|
# Checks if the conditions reference a table other than the current model table
|
1753
|
-
def include_eager_conditions?(options, tables = nil)
|
1754
|
-
((tables || conditions_tables(options)) -
|
1689
|
+
def include_eager_conditions?(options, tables = nil, joined_tables = nil)
|
1690
|
+
((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any?
|
1755
1691
|
end
|
1756
1692
|
|
1757
1693
|
# Checks if the query order references a table other than the current model's table.
|
1758
|
-
def include_eager_order?(options, tables = nil)
|
1759
|
-
((tables || order_tables(options)) -
|
1694
|
+
def include_eager_order?(options, tables = nil, joined_tables = nil)
|
1695
|
+
((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any?
|
1760
1696
|
end
|
1761
1697
|
|
1762
|
-
def include_eager_select?(options)
|
1763
|
-
(selects_tables(options) -
|
1698
|
+
def include_eager_select?(options, joined_tables = nil)
|
1699
|
+
(selects_tables(options) - (joined_tables || joined_tables(options))).any?
|
1764
1700
|
end
|
1765
1701
|
|
1766
1702
|
def references_eager_loaded_tables?(options)
|
1767
|
-
|
1703
|
+
joined_tables = joined_tables(options)
|
1704
|
+
include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables)
|
1768
1705
|
end
|
1769
1706
|
|
1770
1707
|
def using_limitable_reflections?(reflections)
|
@@ -1919,9 +1856,10 @@ module ActiveRecord
|
|
1919
1856
|
def construct(parent, associations, joins, row)
|
1920
1857
|
case associations
|
1921
1858
|
when Symbol, String
|
1922
|
-
|
1923
|
-
|
1924
|
-
|
1859
|
+
join = joins.detect{|j| j.reflection.name.to_s == associations.to_s && j.parent_table_name == parent.class.table_name }
|
1860
|
+
raise(ConfigurationError, "No such association") if join.nil?
|
1861
|
+
|
1862
|
+
joins.delete(join)
|
1925
1863
|
construct_association(parent, join, row)
|
1926
1864
|
when Array
|
1927
1865
|
associations.each do |association|
|
@@ -1929,7 +1867,11 @@ module ActiveRecord
|
|
1929
1867
|
end
|
1930
1868
|
when Hash
|
1931
1869
|
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
|
1932
|
-
|
1870
|
+
join = joins.detect{|j| j.reflection.name.to_s == name.to_s && j.parent_table_name == parent.class.table_name }
|
1871
|
+
raise(ConfigurationError, "No such association") if join.nil?
|
1872
|
+
|
1873
|
+
association = construct_association(parent, join, row)
|
1874
|
+
joins.delete(join)
|
1933
1875
|
construct(association, associations[name], joins, row) if association
|
1934
1876
|
end
|
1935
1877
|
else
|
@@ -2141,7 +2083,7 @@ module ActiveRecord
|
|
2141
2083
|
aliased_table_name,
|
2142
2084
|
foreign_key,
|
2143
2085
|
parent.aliased_table_name,
|
2144
|
-
parent.primary_key
|
2086
|
+
reflection.options[:primary_key] || parent.primary_key
|
2145
2087
|
]
|
2146
2088
|
end
|
2147
2089
|
when :belongs_to
|
@@ -2159,7 +2101,7 @@ module ActiveRecord
|
|
2159
2101
|
klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record?
|
2160
2102
|
|
2161
2103
|
[through_reflection, reflection].each do |ref|
|
2162
|
-
join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]
|
2104
|
+
join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
|
2163
2105
|
end
|
2164
2106
|
|
2165
2107
|
join
|
@@ -2168,7 +2110,7 @@ module ActiveRecord
|
|
2168
2110
|
protected
|
2169
2111
|
|
2170
2112
|
def aliased_table_name_for(name, suffix = nil)
|
2171
|
-
if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{name.downcase}\son}
|
2113
|
+
if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{active_record.connection.quote_table_name name.downcase}\son}
|
2172
2114
|
@join_dependency.table_aliases[name] += 1
|
2173
2115
|
end
|
2174
2116
|
|