activerecord 2.1.2 → 2.2.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 +32 -6
- data/README +0 -0
- data/Rakefile +4 -5
- data/lib/active_record.rb +11 -10
- data/lib/active_record/aggregations.rb +110 -38
- data/lib/active_record/association_preload.rb +104 -15
- data/lib/active_record/associations.rb +427 -212
- data/lib/active_record/associations/association_collection.rb +101 -16
- data/lib/active_record/associations/association_proxy.rb +65 -13
- data/lib/active_record/associations/belongs_to_association.rb +2 -2
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +13 -3
- data/lib/active_record/associations/has_many_association.rb +28 -28
- data/lib/active_record/associations/has_many_through_association.rb +21 -19
- data/lib/active_record/associations/has_one_association.rb +24 -7
- data/lib/active_record/associations/has_one_through_association.rb +3 -4
- data/lib/active_record/attribute_methods.rb +13 -5
- data/lib/active_record/base.rb +435 -212
- data/lib/active_record/calculations.rb +12 -5
- data/lib/active_record/callbacks.rb +28 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +355 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +42 -215
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -5
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +48 -7
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -4
- data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -26
- data/lib/active_record/connection_adapters/mysql_adapter.rb +71 -45
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +155 -84
- data/lib/active_record/dirty.rb +25 -7
- data/lib/active_record/dynamic_finder_match.rb +41 -0
- data/lib/active_record/fixtures.rb +10 -9
- data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
- data/lib/active_record/locale/en.yml +54 -0
- data/lib/active_record/migration.rb +47 -10
- data/lib/active_record/named_scope.rb +29 -16
- data/lib/active_record/reflection.rb +118 -54
- data/lib/active_record/schema_dumper.rb +13 -7
- data/lib/active_record/test_case.rb +18 -5
- data/lib/active_record/transactions.rb +89 -34
- data/lib/active_record/validations.rb +270 -180
- data/lib/active_record/version.rb +1 -1
- data/test/cases/active_schema_test_mysql.rb +5 -0
- data/test/cases/adapter_test.rb +6 -0
- data/test/cases/aggregations_test.rb +39 -0
- data/test/cases/associations/belongs_to_associations_test.rb +10 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +30 -12
- data/test/cases/associations/eager_test.rb +54 -5
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +77 -10
- data/test/cases/associations/has_many_associations_test.rb +74 -7
- data/test/cases/associations/has_many_through_associations_test.rb +50 -3
- data/test/cases/associations/has_one_associations_test.rb +17 -0
- data/test/cases/associations/has_one_through_associations_test.rb +49 -1
- data/test/cases/associations_test.rb +0 -0
- data/test/cases/attribute_methods_test.rb +59 -4
- data/test/cases/base_test.rb +93 -21
- data/test/cases/binary_test.rb +1 -5
- data/test/cases/calculations_test.rb +5 -0
- data/test/cases/callbacks_observers_test.rb +38 -0
- data/test/cases/connection_test_mysql.rb +1 -1
- data/test/cases/defaults_test.rb +32 -1
- data/test/cases/deprecated_finder_test.rb +0 -0
- data/test/cases/dirty_test.rb +13 -0
- data/test/cases/finder_test.rb +162 -12
- data/test/cases/fixtures_test.rb +32 -3
- data/test/cases/helper.rb +15 -0
- data/test/cases/i18n_test.rb +41 -0
- data/test/cases/inheritance_test.rb +2 -2
- data/test/cases/lifecycle_test.rb +0 -0
- data/test/cases/locking_test.rb +4 -9
- data/test/cases/method_scoping_test.rb +109 -2
- data/test/cases/migration_test.rb +43 -8
- data/test/cases/multiple_db_test.rb +25 -0
- data/test/cases/named_scope_test.rb +74 -0
- data/test/cases/pooled_connections_test.rb +103 -0
- data/test/cases/readonly_test.rb +0 -0
- data/test/cases/reflection_test.rb +11 -3
- data/test/cases/reload_models_test.rb +20 -0
- data/test/cases/sanitize_test.rb +25 -0
- data/test/cases/schema_authorization_test_postgresql.rb +2 -2
- data/test/cases/transactions_test.rb +62 -12
- data/test/cases/unconnected_test.rb +0 -0
- data/test/cases/validations_i18n_test.rb +921 -0
- data/test/cases/validations_test.rb +44 -33
- data/test/connections/native_mysql/connection.rb +1 -3
- data/test/fixtures/companies.yml +1 -0
- data/test/fixtures/customers.yml +10 -1
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/fixtures/organizations.yml +5 -0
- data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
- data/test/models/author.rb +3 -0
- data/test/models/category.rb +3 -0
- data/test/models/club.rb +6 -0
- data/test/models/company.rb +25 -1
- data/test/models/customer.rb +19 -1
- data/test/models/member.rb +2 -0
- data/test/models/member_detail.rb +4 -0
- data/test/models/organization.rb +4 -0
- data/test/models/parrot.rb +1 -0
- data/test/models/post.rb +3 -0
- data/test/models/reply.rb +0 -0
- data/test/models/topic.rb +3 -0
- data/test/schema/schema.rb +12 -1
- metadata +22 -10
- data/lib/active_record/vendor/mysql.rb +0 -1214
- data/test/cases/adapter_test_sqlserver.rb +0 -95
- data/test/cases/table_name_test_sqlserver.rb +0 -23
- data/test/cases/threaded_connections_test.rb +0 -48
- data/test/schema/sqlserver_specific_schema.rb +0 -5
@@ -2,6 +2,19 @@ require 'set'
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
|
+
# AssociationCollection is an abstract class that provides common stuff to
|
6
|
+
# ease the implementation of association proxies that represent
|
7
|
+
# collections. See the class hierarchy in AssociationProxy.
|
8
|
+
#
|
9
|
+
# You need to be careful with assumptions regarding the target: The proxy
|
10
|
+
# does not fetch records from the database until it needs them, but new
|
11
|
+
# ones created with +build+ are added to the target. So, the target may be
|
12
|
+
# non-empty and still lack children waiting to be read from the database.
|
13
|
+
# If you look directly to the database you cannot assume that's the entire
|
14
|
+
# collection because new records may have beed added to the target, etc.
|
15
|
+
#
|
16
|
+
# If you need to work on all current children, new and existing records,
|
17
|
+
# +load_target+ and the +loaded+ flag are your friends.
|
5
18
|
class AssociationCollection < AssociationProxy #:nodoc:
|
6
19
|
def initialize(owner, reflection)
|
7
20
|
super
|
@@ -14,7 +27,7 @@ module ActiveRecord
|
|
14
27
|
# If using a custom finder_sql, scan the entire collection.
|
15
28
|
if @reflection.options[:finder_sql]
|
16
29
|
expects_array = args.first.kind_of?(Array)
|
17
|
-
ids = args.flatten.compact.uniq.map
|
30
|
+
ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
|
18
31
|
|
19
32
|
if ids.size == 1
|
20
33
|
id = ids.first
|
@@ -50,7 +63,7 @@ module ActiveRecord
|
|
50
63
|
|
51
64
|
# Fetches the first one using SQL if possible.
|
52
65
|
def first(*args)
|
53
|
-
if fetch_first_or_last_using_find?
|
66
|
+
if fetch_first_or_last_using_find?(args)
|
54
67
|
find(:first, *args)
|
55
68
|
else
|
56
69
|
load_target unless loaded?
|
@@ -60,7 +73,7 @@ module ActiveRecord
|
|
60
73
|
|
61
74
|
# Fetches the last one using SQL if possible.
|
62
75
|
def last(*args)
|
63
|
-
if fetch_first_or_last_using_find?
|
76
|
+
if fetch_first_or_last_using_find?(args)
|
64
77
|
find(:last, *args)
|
65
78
|
else
|
66
79
|
load_target unless loaded?
|
@@ -95,7 +108,7 @@ module ActiveRecord
|
|
95
108
|
result = true
|
96
109
|
load_target if @owner.new_record?
|
97
110
|
|
98
|
-
|
111
|
+
transaction do
|
99
112
|
flatten_deeper(records).each do |record|
|
100
113
|
raise_on_type_mismatch(record)
|
101
114
|
add_record_to_target_with_callbacks(record) do |r|
|
@@ -110,6 +123,21 @@ module ActiveRecord
|
|
110
123
|
alias_method :push, :<<
|
111
124
|
alias_method :concat, :<<
|
112
125
|
|
126
|
+
# Starts a transaction in the association class's database connection.
|
127
|
+
#
|
128
|
+
# class Author < ActiveRecord::Base
|
129
|
+
# has_many :books
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# Author.find(:first).books.transaction do
|
133
|
+
# # same effect as calling Book.transaction
|
134
|
+
# end
|
135
|
+
def transaction(*args)
|
136
|
+
@reflection.klass.transaction(*args) do
|
137
|
+
yield
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
113
141
|
# Remove all records from this association
|
114
142
|
def delete_all
|
115
143
|
load_target
|
@@ -126,12 +154,47 @@ module ActiveRecord
|
|
126
154
|
end
|
127
155
|
end
|
128
156
|
|
129
|
-
#
|
157
|
+
# Count all records using SQL. If the +:counter_sql+ option is set for the association, it will
|
158
|
+
# be used for the query. If no +:counter_sql+ was supplied, but +:finder_sql+ was set, the
|
159
|
+
# descendant's +construct_sql+ method will have set :counter_sql automatically.
|
160
|
+
# Otherwise, construct options and pass them with scope to the target class's +count+.
|
161
|
+
def count(*args)
|
162
|
+
if @reflection.options[:counter_sql]
|
163
|
+
@reflection.klass.count_by_sql(@counter_sql)
|
164
|
+
else
|
165
|
+
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
|
166
|
+
if @reflection.options[:uniq]
|
167
|
+
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
168
|
+
column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
|
169
|
+
options.merge!(:distinct => true)
|
170
|
+
end
|
171
|
+
|
172
|
+
value = @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
|
173
|
+
|
174
|
+
limit = @reflection.options[:limit]
|
175
|
+
offset = @reflection.options[:offset]
|
176
|
+
|
177
|
+
if limit || offset
|
178
|
+
[ [value - offset.to_i, 0].max, limit.to_i ].min
|
179
|
+
else
|
180
|
+
value
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
# Removes +records+ from this association calling +before_remove+ and
|
187
|
+
# +after_remove+ callbacks.
|
188
|
+
#
|
189
|
+
# This method is abstract in the sense that +delete_records+ has to be
|
190
|
+
# provided by descendants. Note this method does not imply the records
|
191
|
+
# are actually removed from the database, that depends precisely on
|
192
|
+
# +delete_records+. They are in any case removed from the collection.
|
130
193
|
def delete(*records)
|
131
194
|
records = flatten_deeper(records)
|
132
195
|
records.each { |record| raise_on_type_mismatch(record) }
|
133
196
|
|
134
|
-
|
197
|
+
transaction do
|
135
198
|
records.each { |record| callback(:before_remove, record) }
|
136
199
|
|
137
200
|
old_records = records.reject {|r| r.new_record? }
|
@@ -158,7 +221,7 @@ module ActiveRecord
|
|
158
221
|
end
|
159
222
|
|
160
223
|
def destroy_all
|
161
|
-
|
224
|
+
transaction do
|
162
225
|
each { |record| record.destroy }
|
163
226
|
end
|
164
227
|
|
@@ -183,12 +246,21 @@ module ActiveRecord
|
|
183
246
|
end
|
184
247
|
end
|
185
248
|
|
186
|
-
# Returns the size of the collection by executing a SELECT COUNT(*)
|
187
|
-
#
|
188
|
-
#
|
249
|
+
# Returns the size of the collection by executing a SELECT COUNT(*)
|
250
|
+
# query if the collection hasn't been loaded, and calling
|
251
|
+
# <tt>collection.size</tt> if it has.
|
252
|
+
#
|
253
|
+
# If the collection has been already loaded +size+ and +length+ are
|
254
|
+
# equivalent. If not and you are going to need the records anyway
|
255
|
+
# +length+ will take one less query. Otherwise +size+ is more efficient.
|
256
|
+
#
|
257
|
+
# This method is abstract in the sense that it relies on
|
258
|
+
# +count_records+, which is a method descendants have to provide.
|
189
259
|
def size
|
190
260
|
if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
|
191
261
|
@target.size
|
262
|
+
elsif !loaded? && @reflection.options[:group]
|
263
|
+
load_target.size
|
192
264
|
elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
|
193
265
|
unsaved_records = @target.select { |r| r.new_record? }
|
194
266
|
unsaved_records.size + count_records
|
@@ -197,12 +269,18 @@ module ActiveRecord
|
|
197
269
|
end
|
198
270
|
end
|
199
271
|
|
200
|
-
# Returns the size of the collection
|
201
|
-
#
|
272
|
+
# Returns the size of the collection calling +size+ on the target.
|
273
|
+
#
|
274
|
+
# If the collection has been already loaded +length+ and +size+ are
|
275
|
+
# equivalent. If not and you are going to need the records anyway this
|
276
|
+
# method will take one less query. Otherwise +size+ is more efficient.
|
202
277
|
def length
|
203
278
|
load_target.size
|
204
279
|
end
|
205
280
|
|
281
|
+
# Equivalent to <tt>collection.size.zero?</tt>. If the collection has
|
282
|
+
# not been already loaded and you are going to fetch the records anyway
|
283
|
+
# it is better to check <tt>collection.length.zero?</tt>.
|
206
284
|
def empty?
|
207
285
|
size.zero?
|
208
286
|
end
|
@@ -235,7 +313,7 @@ module ActiveRecord
|
|
235
313
|
other = other_array.size < 100 ? other_array : other_array.to_set
|
236
314
|
current = @target.size < 100 ? @target : @target.to_set
|
237
315
|
|
238
|
-
|
316
|
+
transaction do
|
239
317
|
delete(@target.select { |v| !other.include?(v) })
|
240
318
|
concat(other_array.select { |v| !current.include?(v) })
|
241
319
|
end
|
@@ -248,6 +326,10 @@ module ActiveRecord
|
|
248
326
|
exists?(record)
|
249
327
|
end
|
250
328
|
|
329
|
+
def proxy_respond_to?(method, include_private = false)
|
330
|
+
super || @reflection.klass.respond_to?(method, include_private)
|
331
|
+
end
|
332
|
+
|
251
333
|
protected
|
252
334
|
def construct_find_options!(options)
|
253
335
|
end
|
@@ -316,7 +398,9 @@ module ActiveRecord
|
|
316
398
|
def create_record(attrs)
|
317
399
|
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
|
318
400
|
ensure_owner_is_not_new
|
319
|
-
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create])
|
401
|
+
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
|
402
|
+
@reflection.build_association(attrs)
|
403
|
+
end
|
320
404
|
if block_given?
|
321
405
|
add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
|
322
406
|
else
|
@@ -326,7 +410,7 @@ module ActiveRecord
|
|
326
410
|
|
327
411
|
def build_record(attrs)
|
328
412
|
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
|
329
|
-
record = @reflection.
|
413
|
+
record = @reflection.build_association(attrs)
|
330
414
|
if block_given?
|
331
415
|
add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
|
332
416
|
else
|
@@ -361,7 +445,8 @@ module ActiveRecord
|
|
361
445
|
end
|
362
446
|
|
363
447
|
def fetch_first_or_last_using_find?(args)
|
364
|
-
args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] ||
|
448
|
+
args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] ||
|
449
|
+
@target.any? { |record| record.new_record? } || args.first.kind_of?(Integer))
|
365
450
|
end
|
366
451
|
end
|
367
452
|
end
|
@@ -39,7 +39,7 @@ module ActiveRecord
|
|
39
39
|
# though the object behind <tt>blog.posts</tt> is not an Array, but an
|
40
40
|
# ActiveRecord::Associations::HasManyAssociation.
|
41
41
|
#
|
42
|
-
# The <tt>@target</tt> object is not loaded until needed. For example,
|
42
|
+
# The <tt>@target</tt> object is not \loaded until needed. For example,
|
43
43
|
#
|
44
44
|
# blog.posts.count
|
45
45
|
#
|
@@ -57,76 +57,109 @@ module ActiveRecord
|
|
57
57
|
reset
|
58
58
|
end
|
59
59
|
|
60
|
+
# Returns the owner of the proxy.
|
60
61
|
def proxy_owner
|
61
62
|
@owner
|
62
63
|
end
|
63
64
|
|
65
|
+
# Returns the reflection object that represents the association handled
|
66
|
+
# by the proxy.
|
64
67
|
def proxy_reflection
|
65
68
|
@reflection
|
66
69
|
end
|
67
70
|
|
71
|
+
# Returns the \target of the proxy, same as +target+.
|
68
72
|
def proxy_target
|
69
73
|
@target
|
70
74
|
end
|
71
75
|
|
76
|
+
# Does the proxy or its \target respond to +symbol+?
|
72
77
|
def respond_to?(*args)
|
73
78
|
proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
|
74
79
|
end
|
75
80
|
|
76
|
-
#
|
77
|
-
# doesn't catch it.
|
81
|
+
# Forwards <tt>===</tt> explicitly to the \target because the instance method
|
82
|
+
# removal above doesn't catch it. Loads the \target if needed.
|
78
83
|
def ===(other)
|
79
84
|
load_target
|
80
85
|
other === @target
|
81
86
|
end
|
82
87
|
|
88
|
+
# Returns the name of the table of the related class:
|
89
|
+
#
|
90
|
+
# post.comments.aliased_table_name # => "comments"
|
91
|
+
#
|
83
92
|
def aliased_table_name
|
84
93
|
@reflection.klass.table_name
|
85
94
|
end
|
86
95
|
|
96
|
+
# Returns the SQL string that corresponds to the <tt>:conditions</tt>
|
97
|
+
# option of the macro, if given, or +nil+ otherwise.
|
87
98
|
def conditions
|
88
|
-
@conditions ||= interpolate_sql(
|
99
|
+
@conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions
|
89
100
|
end
|
90
101
|
alias :sql_conditions :conditions
|
91
102
|
|
103
|
+
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
|
92
104
|
def reset
|
93
105
|
@loaded = false
|
94
106
|
@target = nil
|
95
107
|
end
|
96
108
|
|
109
|
+
# Reloads the \target and returns +self+ on success.
|
97
110
|
def reload
|
98
111
|
reset
|
99
112
|
load_target
|
100
113
|
self unless @target.nil?
|
101
114
|
end
|
102
115
|
|
116
|
+
# Has the \target been already \loaded?
|
103
117
|
def loaded?
|
104
118
|
@loaded
|
105
119
|
end
|
106
120
|
|
121
|
+
# Asserts the \target has been loaded setting the \loaded flag to +true+.
|
107
122
|
def loaded
|
108
123
|
@loaded = true
|
109
124
|
end
|
110
125
|
|
126
|
+
# Returns the target of this proxy, same as +proxy_target+.
|
111
127
|
def target
|
112
128
|
@target
|
113
129
|
end
|
114
130
|
|
131
|
+
# Sets the target of this proxy to <tt>\target</tt>, and the \loaded flag to +true+.
|
115
132
|
def target=(target)
|
116
133
|
@target = target
|
117
134
|
loaded
|
118
135
|
end
|
119
136
|
|
137
|
+
# Forwards the call to the target. Loads the \target if needed.
|
120
138
|
def inspect
|
121
139
|
load_target
|
122
140
|
@target.inspect
|
123
141
|
end
|
124
142
|
|
143
|
+
def send(method, *args)
|
144
|
+
if proxy_respond_to?(method)
|
145
|
+
super
|
146
|
+
else
|
147
|
+
load_target
|
148
|
+
@target.send(method, *args)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
125
152
|
protected
|
153
|
+
# Does the association have a <tt>:dependent</tt> option?
|
126
154
|
def dependent?
|
127
155
|
@reflection.options[:dependent]
|
128
156
|
end
|
129
157
|
|
158
|
+
# Returns a string with the IDs of +records+ joined with a comma, quoted
|
159
|
+
# if needed. The result is ready to be inserted into a SQL IN clause.
|
160
|
+
#
|
161
|
+
# quoted_record_ids(records) # => "23,56,58,67"
|
162
|
+
#
|
130
163
|
def quoted_record_ids(records)
|
131
164
|
records.map { |record| record.quoted_id }.join(',')
|
132
165
|
end
|
@@ -135,10 +168,13 @@ module ActiveRecord
|
|
135
168
|
@owner.send(:interpolate_sql, sql, record)
|
136
169
|
end
|
137
170
|
|
171
|
+
# Forwards the call to the reflection class.
|
138
172
|
def sanitize_sql(sql)
|
139
173
|
@reflection.klass.send(:sanitize_sql, sql)
|
140
174
|
end
|
141
175
|
|
176
|
+
# Assigns the ID of the owner to the corresponding foreign key in +record+.
|
177
|
+
# If the association is polymorphic the type of the owner is also set.
|
142
178
|
def set_belongs_to_association_for(record)
|
143
179
|
if @reflection.options[:as]
|
144
180
|
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
|
@@ -148,6 +184,7 @@ module ActiveRecord
|
|
148
184
|
end
|
149
185
|
end
|
150
186
|
|
187
|
+
# Merges into +options+ the ones coming from the reflection.
|
151
188
|
def merge_options_from_reflection!(options)
|
152
189
|
options.reverse_merge!(
|
153
190
|
:group => @reflection.options[:group],
|
@@ -160,13 +197,17 @@ module ActiveRecord
|
|
160
197
|
)
|
161
198
|
end
|
162
199
|
|
200
|
+
# Forwards +with_scope+ to the reflection.
|
163
201
|
def with_scope(*args, &block)
|
164
202
|
@reflection.klass.send :with_scope, *args, &block
|
165
203
|
end
|
166
204
|
|
167
205
|
private
|
206
|
+
# Forwards any missing method call to the \target.
|
168
207
|
def method_missing(method, *args)
|
169
208
|
if load_target
|
209
|
+
raise NoMethodError unless @target.respond_to?(method)
|
210
|
+
|
170
211
|
if block_given?
|
171
212
|
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
172
213
|
else
|
@@ -175,16 +216,16 @@ module ActiveRecord
|
|
175
216
|
end
|
176
217
|
end
|
177
218
|
|
178
|
-
# Loads the target if needed and returns it.
|
219
|
+
# Loads the \target if needed and returns it.
|
179
220
|
#
|
180
221
|
# This method is abstract in the sense that it relies on +find_target+,
|
181
222
|
# which is expected to be provided by descendants.
|
182
223
|
#
|
183
|
-
# If the target is already loaded it is just returned. Thus, you can call
|
184
|
-
# +load_target+ unconditionally to get the target.
|
224
|
+
# If the \target is already \loaded it is just returned. Thus, you can call
|
225
|
+
# +load_target+ unconditionally to get the \target.
|
185
226
|
#
|
186
227
|
# ActiveRecord::RecordNotFound is rescued within the method, and it is
|
187
|
-
# not reraised. The proxy is reset and +nil+ is the return value.
|
228
|
+
# not reraised. The proxy is \reset and +nil+ is the return value.
|
188
229
|
def load_target
|
189
230
|
return nil unless defined?(@loaded)
|
190
231
|
|
@@ -198,22 +239,33 @@ module ActiveRecord
|
|
198
239
|
reset
|
199
240
|
end
|
200
241
|
|
201
|
-
# Can be overwritten by associations that might have the foreign key
|
202
|
-
# having the object itself (and
|
242
|
+
# Can be overwritten by associations that might have the foreign key
|
243
|
+
# available for an association without having the object itself (and
|
244
|
+
# still being a new record). Currently, only +belongs_to+ presents
|
245
|
+
# this scenario (both vanilla and polymorphic).
|
203
246
|
def foreign_key_present
|
204
247
|
false
|
205
248
|
end
|
206
249
|
|
250
|
+
# Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
|
251
|
+
# the kind of the class of the associated objects. Meant to be used as
|
252
|
+
# a sanity check when you are about to assign an associated record.
|
207
253
|
def raise_on_type_mismatch(record)
|
208
|
-
unless record.is_a?(@reflection.klass)
|
254
|
+
unless record.is_a?(@reflection.klass) || record.is_a?(@reflection.class_name.constantize)
|
209
255
|
message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
|
210
256
|
raise ActiveRecord::AssociationTypeMismatch, message
|
211
257
|
end
|
212
258
|
end
|
213
259
|
|
214
|
-
# Array#flatten has problems with recursive arrays. Going one level
|
260
|
+
# Array#flatten has problems with recursive arrays. Going one level
|
261
|
+
# deeper solves the majority of the problems.
|
215
262
|
def flatten_deeper(array)
|
216
|
-
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
|
263
|
+
array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten
|
264
|
+
end
|
265
|
+
|
266
|
+
# Returns the ID of the owner, quoted if needed.
|
267
|
+
def owner_quoted_id
|
268
|
+
@owner.quoted_id
|
217
269
|
end
|
218
270
|
end
|
219
271
|
end
|
@@ -2,11 +2,11 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
class BelongsToAssociation < AssociationProxy #:nodoc:
|
4
4
|
def create(attributes = {})
|
5
|
-
replace(@reflection.
|
5
|
+
replace(@reflection.create_association(attributes))
|
6
6
|
end
|
7
7
|
|
8
8
|
def build(attributes = {})
|
9
|
-
replace(@reflection.
|
9
|
+
replace(@reflection.build_association(attributes))
|
10
10
|
end
|
11
11
|
|
12
12
|
def replace(record)
|