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.

Files changed (93) hide show
  1. data/CHANGELOG +6023 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +162 -0
  5. data/examples/simple.rb +14 -0
  6. data/lib/active_record.rb +124 -0
  7. data/lib/active_record/aggregations.rb +277 -0
  8. data/lib/active_record/association_preload.rb +403 -0
  9. data/lib/active_record/associations.rb +2254 -0
  10. data/lib/active_record/associations/association_collection.rb +562 -0
  11. data/lib/active_record/associations/association_proxy.rb +295 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +137 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  17. data/lib/active_record/associations/has_one_association.rb +143 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  19. data/lib/active_record/associations/through_association_scope.rb +154 -0
  20. data/lib/active_record/attribute_methods.rb +60 -0
  21. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +116 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  27. data/lib/active_record/attribute_methods/write.rb +37 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1867 -0
  30. data/lib/active_record/callbacks.rb +288 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  33. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +212 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +643 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1030 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  43. data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
  44. data/lib/active_record/counter_cache.rb +115 -0
  45. data/lib/active_record/dynamic_finder_match.rb +53 -0
  46. data/lib/active_record/dynamic_scope_match.rb +32 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1008 -0
  49. data/lib/active_record/locale/en.yml +40 -0
  50. data/lib/active_record/locking/optimistic.rb +172 -0
  51. data/lib/active_record/locking/pessimistic.rb +55 -0
  52. data/lib/active_record/log_subscriber.rb +48 -0
  53. data/lib/active_record/migration.rb +617 -0
  54. data/lib/active_record/named_scope.rb +138 -0
  55. data/lib/active_record/nested_attributes.rb +417 -0
  56. data/lib/active_record/observer.rb +140 -0
  57. data/lib/active_record/persistence.rb +291 -0
  58. data/lib/active_record/query_cache.rb +36 -0
  59. data/lib/active_record/railtie.rb +91 -0
  60. data/lib/active_record/railties/controller_runtime.rb +38 -0
  61. data/lib/active_record/railties/databases.rake +512 -0
  62. data/lib/active_record/reflection.rb +403 -0
  63. data/lib/active_record/relation.rb +393 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +286 -0
  66. data/lib/active_record/relation/finder_methods.rb +355 -0
  67. data/lib/active_record/relation/predicate_builder.rb +41 -0
  68. data/lib/active_record/relation/query_methods.rb +261 -0
  69. data/lib/active_record/relation/spawn_methods.rb +112 -0
  70. data/lib/active_record/schema.rb +59 -0
  71. data/lib/active_record/schema_dumper.rb +195 -0
  72. data/lib/active_record/serialization.rb +60 -0
  73. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  74. data/lib/active_record/session_store.rb +340 -0
  75. data/lib/active_record/test_case.rb +67 -0
  76. data/lib/active_record/timestamp.rb +88 -0
  77. data/lib/active_record/transactions.rb +356 -0
  78. data/lib/active_record/validations.rb +84 -0
  79. data/lib/active_record/validations/associated.rb +48 -0
  80. data/lib/active_record/validations/uniqueness.rb +185 -0
  81. data/lib/active_record/version.rb +9 -0
  82. data/lib/rails/generators/active_record.rb +27 -0
  83. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  84. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  85. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  86. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  87. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  88. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  89. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  90. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  91. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  92. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  93. 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