activerecord 3.0.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 +6023 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +162 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +124 -0
- data/lib/active_record/aggregations.rb +277 -0
- data/lib/active_record/association_preload.rb +403 -0
- data/lib/active_record/associations.rb +2254 -0
- data/lib/active_record/associations/association_collection.rb +562 -0
- data/lib/active_record/associations/association_proxy.rb +295 -0
- data/lib/active_record/associations/belongs_to_association.rb +91 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +137 -0
- data/lib/active_record/associations/has_many_association.rb +128 -0
- data/lib/active_record/associations/has_many_through_association.rb +116 -0
- data/lib/active_record/associations/has_one_association.rb +143 -0
- data/lib/active_record/associations/has_one_through_association.rb +40 -0
- data/lib/active_record/associations/through_association_scope.rb +154 -0
- data/lib/active_record/attribute_methods.rb +60 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
- data/lib/active_record/attribute_methods/dirty.rb +95 -0
- data/lib/active_record/attribute_methods/primary_key.rb +50 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +116 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
- data/lib/active_record/attribute_methods/write.rb +37 -0
- data/lib/active_record/autosave_association.rb +369 -0
- data/lib/active_record/base.rb +1867 -0
- data/lib/active_record/callbacks.rb +288 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +212 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +643 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1030 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
- data/lib/active_record/counter_cache.rb +115 -0
- data/lib/active_record/dynamic_finder_match.rb +53 -0
- data/lib/active_record/dynamic_scope_match.rb +32 -0
- data/lib/active_record/errors.rb +172 -0
- data/lib/active_record/fixtures.rb +1008 -0
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +172 -0
- data/lib/active_record/locking/pessimistic.rb +55 -0
- data/lib/active_record/log_subscriber.rb +48 -0
- data/lib/active_record/migration.rb +617 -0
- data/lib/active_record/named_scope.rb +138 -0
- data/lib/active_record/nested_attributes.rb +417 -0
- data/lib/active_record/observer.rb +140 -0
- data/lib/active_record/persistence.rb +291 -0
- data/lib/active_record/query_cache.rb +36 -0
- data/lib/active_record/railtie.rb +91 -0
- data/lib/active_record/railties/controller_runtime.rb +38 -0
- data/lib/active_record/railties/databases.rake +512 -0
- data/lib/active_record/reflection.rb +403 -0
- data/lib/active_record/relation.rb +393 -0
- data/lib/active_record/relation/batches.rb +89 -0
- data/lib/active_record/relation/calculations.rb +286 -0
- data/lib/active_record/relation/finder_methods.rb +355 -0
- data/lib/active_record/relation/predicate_builder.rb +41 -0
- data/lib/active_record/relation/query_methods.rb +261 -0
- data/lib/active_record/relation/spawn_methods.rb +112 -0
- data/lib/active_record/schema.rb +59 -0
- data/lib/active_record/schema_dumper.rb +195 -0
- data/lib/active_record/serialization.rb +60 -0
- data/lib/active_record/serializers/xml_serializer.rb +244 -0
- data/lib/active_record/session_store.rb +340 -0
- data/lib/active_record/test_case.rb +67 -0
- data/lib/active_record/timestamp.rb +88 -0
- data/lib/active_record/transactions.rb +356 -0
- data/lib/active_record/validations.rb +84 -0
- data/lib/active_record/validations/associated.rb +48 -0
- data/lib/active_record/validations/uniqueness.rb +185 -0
- data/lib/active_record/version.rb +9 -0
- data/lib/rails/generators/active_record.rb +27 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
- metadata +224 -0
@@ -0,0 +1,403 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# = Active Record Reflection
|
3
|
+
module Reflection # :nodoc:
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# Reflection enables to interrogate Active Record classes and objects
|
7
|
+
# about their associations and aggregations. This information can,
|
8
|
+
# for example, be used in a form builder that takes an Active Record object
|
9
|
+
# and creates input fields for all of the attributes depending on their type
|
10
|
+
# and displays the associations to other objects.
|
11
|
+
#
|
12
|
+
# MacroReflection class has info for AggregateReflection and AssociationReflection
|
13
|
+
# classes.
|
14
|
+
module ClassMethods
|
15
|
+
def create_reflection(macro, name, options, active_record)
|
16
|
+
case macro
|
17
|
+
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
|
18
|
+
klass = options[:through] ? ThroughReflection : AssociationReflection
|
19
|
+
reflection = klass.new(macro, name, options, active_record)
|
20
|
+
when :composed_of
|
21
|
+
reflection = AggregateReflection.new(macro, name, options, active_record)
|
22
|
+
end
|
23
|
+
write_inheritable_hash :reflections, name => reflection
|
24
|
+
reflection
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns a hash containing all AssociationReflection objects for the current class.
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# Invoice.reflections
|
31
|
+
# Account.reflections
|
32
|
+
#
|
33
|
+
def reflections
|
34
|
+
read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
38
|
+
def reflect_on_all_aggregations
|
39
|
+
reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
|
43
|
+
#
|
44
|
+
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
|
45
|
+
#
|
46
|
+
def reflect_on_aggregation(aggregation)
|
47
|
+
reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns an array of AssociationReflection objects for all the
|
51
|
+
# associations in the class. If you only want to reflect on a certain
|
52
|
+
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
|
53
|
+
# <tt>:belongs_to</tt>) as the first parameter.
|
54
|
+
#
|
55
|
+
# Example:
|
56
|
+
#
|
57
|
+
# Account.reflect_on_all_associations # returns an array of all associations
|
58
|
+
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
59
|
+
#
|
60
|
+
def reflect_on_all_associations(macro = nil)
|
61
|
+
association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
|
62
|
+
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the AssociationReflection object for the +association+ (use the symbol).
|
66
|
+
#
|
67
|
+
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
68
|
+
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
69
|
+
#
|
70
|
+
def reflect_on_association(association)
|
71
|
+
reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
75
|
+
def reflect_on_all_autosave_associations
|
76
|
+
reflections.values.select { |reflection| reflection.options[:autosave] }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
# Abstract base class for AggregateReflection and AssociationReflection. Objects of
|
82
|
+
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
83
|
+
class MacroReflection
|
84
|
+
attr_reader :active_record
|
85
|
+
|
86
|
+
def initialize(macro, name, options, active_record)
|
87
|
+
@macro, @name, @options, @active_record = macro, name, options, active_record
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the name of the macro.
|
91
|
+
#
|
92
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
|
93
|
+
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
|
94
|
+
attr_reader :name
|
95
|
+
|
96
|
+
# Returns the macro type.
|
97
|
+
#
|
98
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
|
99
|
+
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
|
100
|
+
attr_reader :macro
|
101
|
+
|
102
|
+
# Returns the hash of options used for the macro.
|
103
|
+
#
|
104
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
|
105
|
+
# <tt>has_many :clients</tt> returns +{}+
|
106
|
+
attr_reader :options
|
107
|
+
|
108
|
+
# Returns the class for the macro.
|
109
|
+
#
|
110
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
|
111
|
+
# <tt>has_many :clients</tt> returns the Client class
|
112
|
+
def klass
|
113
|
+
@klass ||= class_name.constantize
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the class name for the macro.
|
117
|
+
#
|
118
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
|
119
|
+
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
|
120
|
+
def class_name
|
121
|
+
@class_name ||= options[:class_name] || derive_class_name
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
|
125
|
+
# and +other_aggregation+ has an options hash assigned to it.
|
126
|
+
def ==(other_aggregation)
|
127
|
+
other_aggregation.kind_of?(self.class) && name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
|
128
|
+
end
|
129
|
+
|
130
|
+
def sanitized_conditions #:nodoc:
|
131
|
+
@sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
def derive_class_name
|
136
|
+
name.to_s.camelize
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# Holds all the meta-data about an aggregation as it was specified in the
|
142
|
+
# Active Record class.
|
143
|
+
class AggregateReflection < MacroReflection #:nodoc:
|
144
|
+
end
|
145
|
+
|
146
|
+
# Holds all the meta-data about an association as it was specified in the
|
147
|
+
# Active Record class.
|
148
|
+
class AssociationReflection < MacroReflection #:nodoc:
|
149
|
+
# Returns the target association's class.
|
150
|
+
#
|
151
|
+
# class Author < ActiveRecord::Base
|
152
|
+
# has_many :books
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# Author.reflect_on_association(:books).klass
|
156
|
+
# # => Book
|
157
|
+
#
|
158
|
+
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
|
159
|
+
# a new association object. Use +build_association+ or +create_association+
|
160
|
+
# instead. This allows plugins to hook into association object creation.
|
161
|
+
def klass
|
162
|
+
@klass ||= active_record.send(:compute_type, class_name)
|
163
|
+
end
|
164
|
+
|
165
|
+
def initialize(macro, name, options, active_record)
|
166
|
+
super
|
167
|
+
@collection = [:has_many, :has_and_belongs_to_many].include?(macro)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns a new, unsaved instance of the associated class. +options+ will
|
171
|
+
# be passed to the class's constructor.
|
172
|
+
def build_association(*options)
|
173
|
+
klass.new(*options)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Creates a new instance of the associated class, and immediately saves it
|
177
|
+
# with ActiveRecord::Base#save. +options+ will be passed to the class's
|
178
|
+
# creation method. Returns the newly created object.
|
179
|
+
def create_association(*options)
|
180
|
+
klass.create(*options)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Creates a new instance of the associated class, and immediately saves it
|
184
|
+
# with ActiveRecord::Base#save!. +options+ will be passed to the class's
|
185
|
+
# creation method. If the created record doesn't pass validations, then an
|
186
|
+
# exception will be raised.
|
187
|
+
#
|
188
|
+
# Returns the newly created object.
|
189
|
+
def create_association!(*options)
|
190
|
+
klass.create!(*options)
|
191
|
+
end
|
192
|
+
|
193
|
+
def table_name
|
194
|
+
@table_name ||= klass.table_name
|
195
|
+
end
|
196
|
+
|
197
|
+
def quoted_table_name
|
198
|
+
@quoted_table_name ||= klass.quoted_table_name
|
199
|
+
end
|
200
|
+
|
201
|
+
def primary_key_name
|
202
|
+
@primary_key_name ||= options[:foreign_key] || derive_primary_key_name
|
203
|
+
end
|
204
|
+
|
205
|
+
def primary_key_column
|
206
|
+
@primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
|
207
|
+
end
|
208
|
+
|
209
|
+
def association_foreign_key
|
210
|
+
@association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
|
211
|
+
end
|
212
|
+
|
213
|
+
def counter_cache_column
|
214
|
+
if options[:counter_cache] == true
|
215
|
+
"#{active_record.name.demodulize.underscore.pluralize}_count"
|
216
|
+
elsif options[:counter_cache]
|
217
|
+
options[:counter_cache]
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def columns(tbl_name, log_msg)
|
222
|
+
@columns ||= klass.connection.columns(tbl_name, log_msg)
|
223
|
+
end
|
224
|
+
|
225
|
+
def reset_column_information
|
226
|
+
@columns = nil
|
227
|
+
end
|
228
|
+
|
229
|
+
def check_validity!
|
230
|
+
check_validity_of_inverse!
|
231
|
+
end
|
232
|
+
|
233
|
+
def check_validity_of_inverse!
|
234
|
+
unless options[:polymorphic]
|
235
|
+
if has_inverse? && inverse_of.nil?
|
236
|
+
raise InverseOfAssociationNotFoundError.new(self)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def through_reflection
|
242
|
+
false
|
243
|
+
end
|
244
|
+
|
245
|
+
def through_reflection_primary_key_name
|
246
|
+
end
|
247
|
+
|
248
|
+
def source_reflection
|
249
|
+
nil
|
250
|
+
end
|
251
|
+
|
252
|
+
def has_inverse?
|
253
|
+
!@options[:inverse_of].nil?
|
254
|
+
end
|
255
|
+
|
256
|
+
def inverse_of
|
257
|
+
if has_inverse?
|
258
|
+
@inverse_of ||= klass.reflect_on_association(options[:inverse_of])
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def polymorphic_inverse_of(associated_class)
|
263
|
+
if has_inverse?
|
264
|
+
if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
|
265
|
+
inverse_relationship
|
266
|
+
else
|
267
|
+
raise InverseOfAssociationNotFoundError.new(self, associated_class)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Returns whether or not this association reflection is for a collection
|
273
|
+
# association. Returns +true+ if the +macro+ is either +has_many+ or
|
274
|
+
# +has_and_belongs_to_many+, +false+ otherwise.
|
275
|
+
def collection?
|
276
|
+
@collection
|
277
|
+
end
|
278
|
+
|
279
|
+
# Returns whether or not the association should be validated as part of
|
280
|
+
# the parent's validation.
|
281
|
+
#
|
282
|
+
# Unless you explicitly disable validation with
|
283
|
+
# <tt>:validate => false</tt>, validation will take place when:
|
284
|
+
#
|
285
|
+
# * you explicitly enable validation; <tt>:validate => true</tt>
|
286
|
+
# * you use autosave; <tt>:autosave => true</tt>
|
287
|
+
# * the association is a +has_many+ association
|
288
|
+
def validate?
|
289
|
+
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
|
290
|
+
end
|
291
|
+
|
292
|
+
def dependent_conditions(record, base_class, extra_conditions)
|
293
|
+
dependent_conditions = []
|
294
|
+
dependent_conditions << "#{primary_key_name} = #{record.send(name).send(:owner_quoted_id)}"
|
295
|
+
dependent_conditions << "#{options[:as]}_type = '#{base_class.name}'" if options[:as]
|
296
|
+
dependent_conditions << klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
297
|
+
dependent_conditions << extra_conditions if extra_conditions
|
298
|
+
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
|
299
|
+
dependent_conditions = dependent_conditions.gsub('@', '\@')
|
300
|
+
dependent_conditions
|
301
|
+
end
|
302
|
+
|
303
|
+
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
304
|
+
def belongs_to?
|
305
|
+
macro == :belongs_to
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
def derive_class_name
|
310
|
+
class_name = name.to_s.camelize
|
311
|
+
class_name = class_name.singularize if collection?
|
312
|
+
class_name
|
313
|
+
end
|
314
|
+
|
315
|
+
def derive_primary_key_name
|
316
|
+
if belongs_to?
|
317
|
+
"#{name}_id"
|
318
|
+
elsif options[:as]
|
319
|
+
"#{options[:as]}_id"
|
320
|
+
else
|
321
|
+
active_record.name.foreign_key
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Holds all the meta-data about a :through association as it was specified
|
327
|
+
# in the Active Record class.
|
328
|
+
class ThroughReflection < AssociationReflection #:nodoc:
|
329
|
+
# Gets the source of the through reflection. It checks both a singularized
|
330
|
+
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
|
331
|
+
#
|
332
|
+
# class Post < ActiveRecord::Base
|
333
|
+
# has_many :taggings
|
334
|
+
# has_many :tags, :through => :taggings
|
335
|
+
# end
|
336
|
+
#
|
337
|
+
def source_reflection
|
338
|
+
@source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
|
339
|
+
end
|
340
|
+
|
341
|
+
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
|
342
|
+
# of a HasManyThrough or HasOneThrough association.
|
343
|
+
#
|
344
|
+
# class Post < ActiveRecord::Base
|
345
|
+
# has_many :taggings
|
346
|
+
# has_many :tags, :through => :taggings
|
347
|
+
# end
|
348
|
+
#
|
349
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
350
|
+
# taggings_reflection = tags_reflection.through_reflection
|
351
|
+
#
|
352
|
+
def through_reflection
|
353
|
+
@through_reflection ||= active_record.reflect_on_association(options[:through])
|
354
|
+
end
|
355
|
+
|
356
|
+
# Gets an array of possible <tt>:through</tt> source reflection names:
|
357
|
+
#
|
358
|
+
# [:singularized, :pluralized]
|
359
|
+
#
|
360
|
+
def source_reflection_names
|
361
|
+
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
|
362
|
+
end
|
363
|
+
|
364
|
+
def check_validity!
|
365
|
+
if through_reflection.nil?
|
366
|
+
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
|
367
|
+
end
|
368
|
+
|
369
|
+
if source_reflection.nil?
|
370
|
+
raise HasManyThroughSourceAssociationNotFoundError.new(self)
|
371
|
+
end
|
372
|
+
|
373
|
+
if options[:source_type] && source_reflection.options[:polymorphic].nil?
|
374
|
+
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
|
375
|
+
end
|
376
|
+
|
377
|
+
if source_reflection.options[:polymorphic] && options[:source_type].nil?
|
378
|
+
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
|
379
|
+
end
|
380
|
+
|
381
|
+
unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
|
382
|
+
raise HasManyThroughSourceAssociationMacroError.new(self)
|
383
|
+
end
|
384
|
+
|
385
|
+
check_validity_of_inverse!
|
386
|
+
end
|
387
|
+
|
388
|
+
def through_reflection_primary_key
|
389
|
+
through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.primary_key_name
|
390
|
+
end
|
391
|
+
|
392
|
+
def through_reflection_primary_key_name
|
393
|
+
through_reflection.primary_key_name if through_reflection.belongs_to?
|
394
|
+
end
|
395
|
+
|
396
|
+
private
|
397
|
+
def derive_class_name
|
398
|
+
# get the class_name of the belongs_to association of the through reflection
|
399
|
+
options[:source_type] || source_reflection.class_name
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
@@ -0,0 +1,393 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
# = Active Record Relation
|
5
|
+
class Relation
|
6
|
+
JoinOperation = Struct.new(:relation, :join_class, :on)
|
7
|
+
ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
|
8
|
+
MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having]
|
9
|
+
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from]
|
10
|
+
|
11
|
+
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
|
12
|
+
|
13
|
+
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
|
14
|
+
delegate :insert, :to => :arel
|
15
|
+
|
16
|
+
attr_reader :table, :klass, :loaded
|
17
|
+
attr_accessor :extensions
|
18
|
+
alias :loaded? :loaded
|
19
|
+
|
20
|
+
def initialize(klass, table)
|
21
|
+
@klass, @table = klass, table
|
22
|
+
|
23
|
+
@implicit_readonly = nil
|
24
|
+
@loaded = false
|
25
|
+
|
26
|
+
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
|
27
|
+
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
|
28
|
+
@extensions = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def new(*args, &block)
|
32
|
+
scoping { @klass.new(*args, &block) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize_copy(other)
|
36
|
+
reset
|
37
|
+
end
|
38
|
+
|
39
|
+
alias build new
|
40
|
+
|
41
|
+
def create(*args, &block)
|
42
|
+
scoping { @klass.create(*args, &block) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def create!(*args, &block)
|
46
|
+
scoping { @klass.create!(*args, &block) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def respond_to?(method, include_private = false)
|
50
|
+
return true if arel.respond_to?(method, include_private) || Array.method_defined?(method) || @klass.respond_to?(method, include_private)
|
51
|
+
|
52
|
+
if match = DynamicFinderMatch.match(method)
|
53
|
+
return true if @klass.send(:all_attributes_exists?, match.attribute_names)
|
54
|
+
elsif match = DynamicScopeMatch.match(method)
|
55
|
+
return true if @klass.send(:all_attributes_exists?, match.attribute_names)
|
56
|
+
else
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_a
|
62
|
+
return @records if loaded?
|
63
|
+
|
64
|
+
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql)
|
65
|
+
|
66
|
+
preload = @preload_values
|
67
|
+
preload += @includes_values unless eager_loading?
|
68
|
+
preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
|
69
|
+
|
70
|
+
# @readonly_value is true only if set explicitly. @implicit_readonly is true if there
|
71
|
+
# are JOINS and no explicit SELECT.
|
72
|
+
readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
|
73
|
+
@records.each { |record| record.readonly! } if readonly
|
74
|
+
|
75
|
+
@loaded = true
|
76
|
+
@records
|
77
|
+
end
|
78
|
+
|
79
|
+
def as_json(options = nil) to_a end #:nodoc:
|
80
|
+
|
81
|
+
# Returns size of the records.
|
82
|
+
def size
|
83
|
+
loaded? ? @records.length : count
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns true if there are no records.
|
87
|
+
def empty?
|
88
|
+
loaded? ? @records.empty? : count.zero?
|
89
|
+
end
|
90
|
+
|
91
|
+
def any?
|
92
|
+
if block_given?
|
93
|
+
to_a.any? { |*block_args| yield(*block_args) }
|
94
|
+
else
|
95
|
+
!empty?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def many?
|
100
|
+
if block_given?
|
101
|
+
to_a.many? { |*block_args| yield(*block_args) }
|
102
|
+
else
|
103
|
+
@limit_value ? to_a.many? : size > 1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Scope all queries to the current scope.
|
108
|
+
#
|
109
|
+
# ==== Example
|
110
|
+
#
|
111
|
+
# Comment.where(:post_id => 1).scoping do
|
112
|
+
# Comment.first # SELECT * FROM comments WHERE post_id = 1
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# Please check unscoped if you want to remove all previous scopes (including
|
116
|
+
# the default_scope) during the execution of a block.
|
117
|
+
def scoping
|
118
|
+
@klass.scoped_methods << self
|
119
|
+
begin
|
120
|
+
yield
|
121
|
+
ensure
|
122
|
+
@klass.scoped_methods.pop
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Updates all records with details given if they match a set of conditions supplied, limits and order can
|
127
|
+
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
|
128
|
+
# database. It does not instantiate the involved models and it does not trigger Active Record callbacks
|
129
|
+
# or validations.
|
130
|
+
#
|
131
|
+
# ==== Parameters
|
132
|
+
#
|
133
|
+
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
|
134
|
+
# * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement.
|
135
|
+
# See conditions in the intro.
|
136
|
+
# * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
|
137
|
+
#
|
138
|
+
# ==== Examples
|
139
|
+
#
|
140
|
+
# # Update all customers with the given attributes
|
141
|
+
# Customer.update_all :wants_email => true
|
142
|
+
#
|
143
|
+
# # Update all books with 'Rails' in their title
|
144
|
+
# Book.update_all "author = 'David'", "title LIKE '%Rails%'"
|
145
|
+
#
|
146
|
+
# # Update all avatars migrated more than a week ago
|
147
|
+
# Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
|
148
|
+
#
|
149
|
+
# # Update all books that match conditions, but limit it to 5 ordered by date
|
150
|
+
# Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
|
151
|
+
def update_all(updates, conditions = nil, options = {})
|
152
|
+
if conditions || options.present?
|
153
|
+
where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
|
154
|
+
else
|
155
|
+
# Apply limit and order only if they're both present
|
156
|
+
if @limit_value.present? == @order_values.present?
|
157
|
+
arel.update(Arel::SqlLiteral.new(@klass.send(:sanitize_sql_for_assignment, updates)))
|
158
|
+
else
|
159
|
+
except(:limit, :order).update_all(updates)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
|
165
|
+
# The resulting object is returned whether the object was saved successfully to the database or not.
|
166
|
+
#
|
167
|
+
# ==== Parameters
|
168
|
+
#
|
169
|
+
# * +id+ - This should be the id or an array of ids to be updated.
|
170
|
+
# * +attributes+ - This should be a hash of attributes or an array of hashes.
|
171
|
+
#
|
172
|
+
# ==== Examples
|
173
|
+
#
|
174
|
+
# # Updates one record
|
175
|
+
# Person.update(15, :user_name => 'Samuel', :group => 'expert')
|
176
|
+
#
|
177
|
+
# # Updates multiple records
|
178
|
+
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
|
179
|
+
# Person.update(people.keys, people.values)
|
180
|
+
def update(id, attributes)
|
181
|
+
if id.is_a?(Array)
|
182
|
+
idx = -1
|
183
|
+
id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
|
184
|
+
else
|
185
|
+
object = find(id)
|
186
|
+
object.update_attributes(attributes)
|
187
|
+
object
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Destroys the records matching +conditions+ by instantiating each
|
192
|
+
# record and calling its +destroy+ method. Each object's callbacks are
|
193
|
+
# executed (including <tt>:dependent</tt> association options and
|
194
|
+
# +before_destroy+/+after_destroy+ Observer methods). Returns the
|
195
|
+
# collection of objects that were destroyed; each will be frozen, to
|
196
|
+
# reflect that no changes should be made (since they can't be
|
197
|
+
# persisted).
|
198
|
+
#
|
199
|
+
# Note: Instantiation, callback execution, and deletion of each
|
200
|
+
# record can be time consuming when you're removing many records at
|
201
|
+
# once. It generates at least one SQL +DELETE+ query per record (or
|
202
|
+
# possibly more, to enforce your callbacks). If you want to delete many
|
203
|
+
# rows quickly, without concern for their associations or callbacks, use
|
204
|
+
# +delete_all+ instead.
|
205
|
+
#
|
206
|
+
# ==== Parameters
|
207
|
+
#
|
208
|
+
# * +conditions+ - A string, array, or hash that specifies which records
|
209
|
+
# to destroy. If omitted, all records are destroyed. See the
|
210
|
+
# Conditions section in the introduction to ActiveRecord::Base for
|
211
|
+
# more information.
|
212
|
+
#
|
213
|
+
# ==== Examples
|
214
|
+
#
|
215
|
+
# Person.destroy_all("last_login < '2004-04-04'")
|
216
|
+
# Person.destroy_all(:status => "inactive")
|
217
|
+
def destroy_all(conditions = nil)
|
218
|
+
if conditions
|
219
|
+
where(conditions).destroy_all
|
220
|
+
else
|
221
|
+
to_a.each {|object| object.destroy }.tap { reset }
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
|
226
|
+
# therefore all callbacks and filters are fired off before the object is deleted. This method is
|
227
|
+
# less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
|
228
|
+
#
|
229
|
+
# This essentially finds the object (or multiple objects) with the given id, creates a new object
|
230
|
+
# from the attributes, and then calls destroy on it.
|
231
|
+
#
|
232
|
+
# ==== Parameters
|
233
|
+
#
|
234
|
+
# * +id+ - Can be either an Integer or an Array of Integers.
|
235
|
+
#
|
236
|
+
# ==== Examples
|
237
|
+
#
|
238
|
+
# # Destroy a single object
|
239
|
+
# Todo.destroy(1)
|
240
|
+
#
|
241
|
+
# # Destroy multiple objects
|
242
|
+
# todos = [1,2,3]
|
243
|
+
# Todo.destroy(todos)
|
244
|
+
def destroy(id)
|
245
|
+
if id.is_a?(Array)
|
246
|
+
id.map { |one_id| destroy(one_id) }
|
247
|
+
else
|
248
|
+
find(id).destroy
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Deletes the records matching +conditions+ without instantiating the records first, and hence not
|
253
|
+
# calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
|
254
|
+
# goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
|
255
|
+
# though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
|
256
|
+
# the number of rows affected.
|
257
|
+
#
|
258
|
+
# ==== Parameters
|
259
|
+
#
|
260
|
+
# * +conditions+ - Conditions are specified the same way as with +find+ method.
|
261
|
+
#
|
262
|
+
# ==== Example
|
263
|
+
#
|
264
|
+
# Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
|
265
|
+
# Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
|
266
|
+
#
|
267
|
+
# Both calls delete the affected posts all at once with a single DELETE statement.
|
268
|
+
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
|
269
|
+
# +after_destroy+ callbacks, use the +destroy_all+ method instead.
|
270
|
+
def delete_all(conditions = nil)
|
271
|
+
conditions ? where(conditions).delete_all : arel.delete.tap { reset }
|
272
|
+
end
|
273
|
+
|
274
|
+
# Deletes the row with a primary key matching the +id+ argument, using a
|
275
|
+
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
|
276
|
+
# Record objects are not instantiated, so the object's callbacks are not
|
277
|
+
# executed, including any <tt>:dependent</tt> association options or
|
278
|
+
# Observer methods.
|
279
|
+
#
|
280
|
+
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
|
281
|
+
#
|
282
|
+
# Note: Although it is often much faster than the alternative,
|
283
|
+
# <tt>#destroy</tt>, skipping callbacks might bypass business logic in
|
284
|
+
# your application that ensures referential integrity or performs other
|
285
|
+
# essential jobs.
|
286
|
+
#
|
287
|
+
# ==== Examples
|
288
|
+
#
|
289
|
+
# # Delete a single row
|
290
|
+
# Todo.delete(1)
|
291
|
+
#
|
292
|
+
# # Delete multiple rows
|
293
|
+
# Todo.delete([2,3,4])
|
294
|
+
def delete(id_or_array)
|
295
|
+
where(@klass.primary_key => id_or_array).delete_all
|
296
|
+
end
|
297
|
+
|
298
|
+
def reload
|
299
|
+
reset
|
300
|
+
to_a # force reload
|
301
|
+
self
|
302
|
+
end
|
303
|
+
|
304
|
+
def reset
|
305
|
+
@first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
|
306
|
+
@should_eager_load = @join_dependency = nil
|
307
|
+
@records = []
|
308
|
+
self
|
309
|
+
end
|
310
|
+
|
311
|
+
def primary_key
|
312
|
+
@primary_key ||= table[@klass.primary_key]
|
313
|
+
end
|
314
|
+
|
315
|
+
def to_sql
|
316
|
+
@to_sql ||= arel.to_sql
|
317
|
+
end
|
318
|
+
|
319
|
+
def where_values_hash
|
320
|
+
Hash[@where_values.find_all { |w|
|
321
|
+
w.respond_to?(:operator) && w.operator == :==
|
322
|
+
}.map { |where|
|
323
|
+
[where.operand1.name,
|
324
|
+
where.operand2.respond_to?(:value) ?
|
325
|
+
where.operand2.value : where.operand2]
|
326
|
+
}]
|
327
|
+
end
|
328
|
+
|
329
|
+
def scope_for_create
|
330
|
+
@scope_for_create ||= begin
|
331
|
+
@create_with_value || where_values_hash
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def eager_loading?
|
336
|
+
@should_eager_load ||= (@eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?))
|
337
|
+
end
|
338
|
+
|
339
|
+
def ==(other)
|
340
|
+
case other
|
341
|
+
when Relation
|
342
|
+
other.to_sql == to_sql
|
343
|
+
when Array
|
344
|
+
to_a == other.to_a
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def inspect
|
349
|
+
to_a.inspect
|
350
|
+
end
|
351
|
+
|
352
|
+
protected
|
353
|
+
|
354
|
+
def method_missing(method, *args, &block)
|
355
|
+
if Array.method_defined?(method)
|
356
|
+
to_a.send(method, *args, &block)
|
357
|
+
elsif @klass.scopes[method]
|
358
|
+
merge(@klass.send(method, *args, &block))
|
359
|
+
elsif @klass.respond_to?(method)
|
360
|
+
scoping { @klass.send(method, *args, &block) }
|
361
|
+
elsif arel.respond_to?(method)
|
362
|
+
arel.send(method, *args, &block)
|
363
|
+
elsif match = DynamicFinderMatch.match(method)
|
364
|
+
attributes = match.attribute_names
|
365
|
+
super unless @klass.send(:all_attributes_exists?, attributes)
|
366
|
+
|
367
|
+
if match.finder?
|
368
|
+
find_by_attributes(match, attributes, *args)
|
369
|
+
elsif match.instantiator?
|
370
|
+
find_or_instantiator_by_attributes(match, attributes, *args, &block)
|
371
|
+
end
|
372
|
+
else
|
373
|
+
super
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
private
|
378
|
+
|
379
|
+
def references_eager_loaded_tables?
|
380
|
+
# always convert table names to downcase as in Oracle quoted table names are in uppercase
|
381
|
+
joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.map{ |t| t.downcase }.uniq
|
382
|
+
(tables_in_string(to_sql) - joined_tables).any?
|
383
|
+
end
|
384
|
+
|
385
|
+
def tables_in_string(string)
|
386
|
+
return [] if string.blank?
|
387
|
+
# always convert table names to downcase as in Oracle quoted table names are in uppercase
|
388
|
+
# ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
|
389
|
+
string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
|
390
|
+
end
|
391
|
+
|
392
|
+
end
|
393
|
+
end
|