square-activerecord 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/CHANGELOG +6140 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +179 -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 +430 -0
  9. data/lib/active_record/associations.rb +2307 -0
  10. data/lib/active_record/associations/association_collection.rb +572 -0
  11. data/lib/active_record/associations/association_proxy.rb +299 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +115 -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 +30 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +56 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +145 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
  27. data/lib/active_record/attribute_methods/write.rb +43 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1904 -0
  30. data/lib/active_record/callbacks.rb +284 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -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 +333 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -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 +539 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -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 +56 -0
  46. data/lib/active_record/dynamic_scope_match.rb +23 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1006 -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 +419 -0
  56. data/lib/active_record/observer.rb +125 -0
  57. data/lib/active_record/persistence.rb +290 -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 +411 -0
  63. data/lib/active_record/relation.rb +394 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +295 -0
  66. data/lib/active_record/relation/finder_methods.rb +363 -0
  67. data/lib/active_record/relation/predicate_builder.rb +48 -0
  68. data/lib/active_record/relation/query_methods.rb +303 -0
  69. data/lib/active_record/relation/spawn_methods.rb +132 -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 +359 -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 +190 -0
  81. data/lib/active_record/version.rb +10 -0
  82. data/lib/rails/generators/active_record.rb +19 -0
  83. data/lib/rails/generators/active_record/migration.rb +15 -0
  84. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  85. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  86. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  87. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  88. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  89. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  90. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  91. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  92. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  93. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  94. metadata +223 -0
@@ -0,0 +1,411 @@
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 association_primary_key
214
+ @association_primary_key ||= options[:primary_key] || klass.primary_key
215
+ end
216
+
217
+ def active_record_primary_key
218
+ @active_record_primary_key ||= options[:primary_key] || active_record.primary_key
219
+ end
220
+
221
+ def counter_cache_column
222
+ if options[:counter_cache] == true
223
+ "#{active_record.name.demodulize.underscore.pluralize}_count"
224
+ elsif options[:counter_cache]
225
+ options[:counter_cache]
226
+ end
227
+ end
228
+
229
+ def columns(tbl_name, log_msg)
230
+ @columns ||= klass.connection.columns(tbl_name, log_msg)
231
+ end
232
+
233
+ def reset_column_information
234
+ @columns = nil
235
+ end
236
+
237
+ def check_validity!
238
+ check_validity_of_inverse!
239
+ end
240
+
241
+ def check_validity_of_inverse!
242
+ unless options[:polymorphic]
243
+ if has_inverse? && inverse_of.nil?
244
+ raise InverseOfAssociationNotFoundError.new(self)
245
+ end
246
+ end
247
+ end
248
+
249
+ def through_reflection
250
+ false
251
+ end
252
+
253
+ def through_reflection_primary_key_name
254
+ end
255
+
256
+ def source_reflection
257
+ nil
258
+ end
259
+
260
+ def has_inverse?
261
+ !@options[:inverse_of].nil?
262
+ end
263
+
264
+ def inverse_of
265
+ if has_inverse?
266
+ @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
267
+ end
268
+ end
269
+
270
+ def polymorphic_inverse_of(associated_class)
271
+ if has_inverse?
272
+ if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
273
+ inverse_relationship
274
+ else
275
+ raise InverseOfAssociationNotFoundError.new(self, associated_class)
276
+ end
277
+ end
278
+ end
279
+
280
+ # Returns whether or not this association reflection is for a collection
281
+ # association. Returns +true+ if the +macro+ is either +has_many+ or
282
+ # +has_and_belongs_to_many+, +false+ otherwise.
283
+ def collection?
284
+ @collection
285
+ end
286
+
287
+ # Returns whether or not the association should be validated as part of
288
+ # the parent's validation.
289
+ #
290
+ # Unless you explicitly disable validation with
291
+ # <tt>:validate => false</tt>, validation will take place when:
292
+ #
293
+ # * you explicitly enable validation; <tt>:validate => true</tt>
294
+ # * you use autosave; <tt>:autosave => true</tt>
295
+ # * the association is a +has_many+ association
296
+ def validate?
297
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
298
+ end
299
+
300
+ def dependent_conditions(record, base_class, extra_conditions)
301
+ dependent_conditions = []
302
+ dependent_conditions << "#{primary_key_name} = #{record.send(name).send(:owner_quoted_id)}"
303
+ dependent_conditions << "#{options[:as]}_type = '#{base_class.name}'" if options[:as]
304
+ dependent_conditions << klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
305
+ dependent_conditions << extra_conditions if extra_conditions
306
+ dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
307
+ dependent_conditions = dependent_conditions.gsub('@', '\@')
308
+ dependent_conditions
309
+ end
310
+
311
+ # Returns +true+ if +self+ is a +belongs_to+ reflection.
312
+ def belongs_to?
313
+ macro == :belongs_to
314
+ end
315
+
316
+ private
317
+ def derive_class_name
318
+ class_name = name.to_s.camelize
319
+ class_name = class_name.singularize if collection?
320
+ class_name
321
+ end
322
+
323
+ def derive_primary_key_name
324
+ if belongs_to?
325
+ "#{name}_id"
326
+ elsif options[:as]
327
+ "#{options[:as]}_id"
328
+ else
329
+ active_record.name.foreign_key
330
+ end
331
+ end
332
+ end
333
+
334
+ # Holds all the meta-data about a :through association as it was specified
335
+ # in the Active Record class.
336
+ class ThroughReflection < AssociationReflection #:nodoc:
337
+ # Gets the source of the through reflection. It checks both a singularized
338
+ # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
339
+ #
340
+ # class Post < ActiveRecord::Base
341
+ # has_many :taggings
342
+ # has_many :tags, :through => :taggings
343
+ # end
344
+ #
345
+ def source_reflection
346
+ @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
347
+ end
348
+
349
+ # Returns the AssociationReflection object specified in the <tt>:through</tt> option
350
+ # of a HasManyThrough or HasOneThrough association.
351
+ #
352
+ # class Post < ActiveRecord::Base
353
+ # has_many :taggings
354
+ # has_many :tags, :through => :taggings
355
+ # end
356
+ #
357
+ # tags_reflection = Post.reflect_on_association(:tags)
358
+ # taggings_reflection = tags_reflection.through_reflection
359
+ #
360
+ def through_reflection
361
+ @through_reflection ||= active_record.reflect_on_association(options[:through])
362
+ end
363
+
364
+ # Gets an array of possible <tt>:through</tt> source reflection names:
365
+ #
366
+ # [:singularized, :pluralized]
367
+ #
368
+ def source_reflection_names
369
+ @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
370
+ end
371
+
372
+ def check_validity!
373
+ if through_reflection.nil?
374
+ raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
375
+ end
376
+
377
+ if source_reflection.nil?
378
+ raise HasManyThroughSourceAssociationNotFoundError.new(self)
379
+ end
380
+
381
+ if options[:source_type] && source_reflection.options[:polymorphic].nil?
382
+ raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
383
+ end
384
+
385
+ if source_reflection.options[:polymorphic] && options[:source_type].nil?
386
+ raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
387
+ end
388
+
389
+ unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
390
+ raise HasManyThroughSourceAssociationMacroError.new(self)
391
+ end
392
+
393
+ check_validity_of_inverse!
394
+ end
395
+
396
+ def through_reflection_primary_key
397
+ through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.primary_key_name
398
+ end
399
+
400
+ def through_reflection_primary_key_name
401
+ through_reflection.primary_key_name if through_reflection.belongs_to?
402
+ end
403
+
404
+ private
405
+ def derive_class_name
406
+ # get the class_name of the belongs_to association of the through reflection
407
+ options[:source_type] || source_reflection.class_name
408
+ end
409
+ end
410
+ end
411
+ end
@@ -0,0 +1,394 @@
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) #:nodoc:
80
+ to_a.as_json(options)
81
+ end
82
+
83
+ # Returns size of the records.
84
+ def size
85
+ loaded? ? @records.length : count
86
+ end
87
+
88
+ # Returns true if there are no records.
89
+ def empty?
90
+ return @records.empty? if loaded?
91
+
92
+ c = count
93
+ c.respond_to?(:zero?) ? c.zero? : c.empty?
94
+ end
95
+
96
+ def any?
97
+ if block_given?
98
+ to_a.any? { |*block_args| yield(*block_args) }
99
+ else
100
+ !empty?
101
+ end
102
+ end
103
+
104
+ def many?
105
+ if block_given?
106
+ to_a.many? { |*block_args| yield(*block_args) }
107
+ else
108
+ @limit_value ? to_a.many? : size > 1
109
+ end
110
+ end
111
+
112
+ # Scope all queries to the current scope.
113
+ #
114
+ # ==== Example
115
+ #
116
+ # Comment.where(:post_id => 1).scoping do
117
+ # Comment.first # SELECT * FROM comments WHERE post_id = 1
118
+ # end
119
+ #
120
+ # Please check unscoped if you want to remove all previous scopes (including
121
+ # the default_scope) during the execution of a block.
122
+ def scoping
123
+ @klass.scoped_methods << self
124
+ begin
125
+ yield
126
+ ensure
127
+ @klass.scoped_methods.pop
128
+ end
129
+ end
130
+
131
+ # Updates all records with details given if they match a set of conditions supplied, limits and order can
132
+ # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
133
+ # database. It does not instantiate the involved models and it does not trigger Active Record callbacks
134
+ # or validations.
135
+ #
136
+ # ==== Parameters
137
+ #
138
+ # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
139
+ # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement.
140
+ # See conditions in the intro.
141
+ # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
142
+ #
143
+ # ==== Examples
144
+ #
145
+ # # Update all customers with the given attributes
146
+ # Customer.update_all :wants_email => true
147
+ #
148
+ # # Update all books with 'Rails' in their title
149
+ # Book.update_all "author = 'David'", "title LIKE '%Rails%'"
150
+ #
151
+ # # Update all avatars migrated more than a week ago
152
+ # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
153
+ #
154
+ # # Update all books that match conditions, but limit it to 5 ordered by date
155
+ # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
156
+ def update_all(updates, conditions = nil, options = {})
157
+ if conditions || options.present?
158
+ where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
159
+ else
160
+ # Apply limit and order only if they're both present
161
+ if @limit_value.present? == @order_values.present?
162
+ arel.update(Arel::SqlLiteral.new(@klass.send(:sanitize_sql_for_assignment, updates)))
163
+ else
164
+ except(:limit, :order).update_all(updates)
165
+ end
166
+ end
167
+ end
168
+
169
+ # Updates an object (or multiple objects) and saves it to the database, if validations pass.
170
+ # The resulting object is returned whether the object was saved successfully to the database or not.
171
+ #
172
+ # ==== Parameters
173
+ #
174
+ # * +id+ - This should be the id or an array of ids to be updated.
175
+ # * +attributes+ - This should be a hash of attributes or an array of hashes.
176
+ #
177
+ # ==== Examples
178
+ #
179
+ # # Updates one record
180
+ # Person.update(15, :user_name => 'Samuel', :group => 'expert')
181
+ #
182
+ # # Updates multiple records
183
+ # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
184
+ # Person.update(people.keys, people.values)
185
+ def update(id, attributes)
186
+ if id.is_a?(Array)
187
+ idx = -1
188
+ id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
189
+ else
190
+ object = find(id)
191
+ object.update_attributes(attributes)
192
+ object
193
+ end
194
+ end
195
+
196
+ # Destroys the records matching +conditions+ by instantiating each
197
+ # record and calling its +destroy+ method. Each object's callbacks are
198
+ # executed (including <tt>:dependent</tt> association options and
199
+ # +before_destroy+/+after_destroy+ Observer methods). Returns the
200
+ # collection of objects that were destroyed; each will be frozen, to
201
+ # reflect that no changes should be made (since they can't be
202
+ # persisted).
203
+ #
204
+ # Note: Instantiation, callback execution, and deletion of each
205
+ # record can be time consuming when you're removing many records at
206
+ # once. It generates at least one SQL +DELETE+ query per record (or
207
+ # possibly more, to enforce your callbacks). If you want to delete many
208
+ # rows quickly, without concern for their associations or callbacks, use
209
+ # +delete_all+ instead.
210
+ #
211
+ # ==== Parameters
212
+ #
213
+ # * +conditions+ - A string, array, or hash that specifies which records
214
+ # to destroy. If omitted, all records are destroyed. See the
215
+ # Conditions section in the introduction to ActiveRecord::Base for
216
+ # more information.
217
+ #
218
+ # ==== Examples
219
+ #
220
+ # Person.destroy_all("last_login < '2004-04-04'")
221
+ # Person.destroy_all(:status => "inactive")
222
+ def destroy_all(conditions = nil)
223
+ if conditions
224
+ where(conditions).destroy_all
225
+ else
226
+ to_a.each {|object| object.destroy }.tap { reset }
227
+ end
228
+ end
229
+
230
+ # Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
231
+ # therefore all callbacks and filters are fired off before the object is deleted. This method is
232
+ # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
233
+ #
234
+ # This essentially finds the object (or multiple objects) with the given id, creates a new object
235
+ # from the attributes, and then calls destroy on it.
236
+ #
237
+ # ==== Parameters
238
+ #
239
+ # * +id+ - Can be either an Integer or an Array of Integers.
240
+ #
241
+ # ==== Examples
242
+ #
243
+ # # Destroy a single object
244
+ # Todo.destroy(1)
245
+ #
246
+ # # Destroy multiple objects
247
+ # todos = [1,2,3]
248
+ # Todo.destroy(todos)
249
+ def destroy(id)
250
+ if id.is_a?(Array)
251
+ id.map { |one_id| destroy(one_id) }
252
+ else
253
+ find(id).destroy
254
+ end
255
+ end
256
+
257
+ # Deletes the records matching +conditions+ without instantiating the records first, and hence not
258
+ # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
259
+ # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
260
+ # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
261
+ # the number of rows affected.
262
+ #
263
+ # ==== Parameters
264
+ #
265
+ # * +conditions+ - Conditions are specified the same way as with +find+ method.
266
+ #
267
+ # ==== Example
268
+ #
269
+ # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
270
+ # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
271
+ #
272
+ # Both calls delete the affected posts all at once with a single DELETE statement.
273
+ # If you need to destroy dependent associations or call your <tt>before_*</tt> or
274
+ # +after_destroy+ callbacks, use the +destroy_all+ method instead.
275
+ def delete_all(conditions = nil)
276
+ conditions ? where(conditions).delete_all : arel.delete.tap { reset }
277
+ end
278
+
279
+ # Deletes the row with a primary key matching the +id+ argument, using a
280
+ # SQL +DELETE+ statement, and returns the number of rows deleted. Active
281
+ # Record objects are not instantiated, so the object's callbacks are not
282
+ # executed, including any <tt>:dependent</tt> association options or
283
+ # Observer methods.
284
+ #
285
+ # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
286
+ #
287
+ # Note: Although it is often much faster than the alternative,
288
+ # <tt>#destroy</tt>, skipping callbacks might bypass business logic in
289
+ # your application that ensures referential integrity or performs other
290
+ # essential jobs.
291
+ #
292
+ # ==== Examples
293
+ #
294
+ # # Delete a single row
295
+ # Todo.delete(1)
296
+ #
297
+ # # Delete multiple rows
298
+ # Todo.delete([2,3,4])
299
+ def delete(id_or_array)
300
+ where(@klass.primary_key => id_or_array).delete_all
301
+ end
302
+
303
+ def reload
304
+ reset
305
+ to_a # force reload
306
+ self
307
+ end
308
+
309
+ def reset
310
+ @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
311
+ @should_eager_load = @join_dependency = nil
312
+ @records = []
313
+ self
314
+ end
315
+
316
+ def primary_key
317
+ @primary_key ||= table[@klass.primary_key]
318
+ end
319
+
320
+ def to_sql
321
+ @to_sql ||= arel.to_sql
322
+ end
323
+
324
+ def where_values_hash
325
+ Hash[@where_values.find_all { |w|
326
+ w.respond_to?(:operator) && w.operator == :== && w.left.relation.name == table_name
327
+ }.map { |where|
328
+ [
329
+ where.left.name,
330
+ where.right.respond_to?(:value) ? where.right.value : where.right
331
+ ]
332
+ }]
333
+ end
334
+
335
+ def scope_for_create
336
+ @scope_for_create ||= begin
337
+ if @create_with_value
338
+ @create_with_value.reverse_merge(where_values_hash)
339
+ else
340
+ where_values_hash
341
+ end
342
+ end
343
+ end
344
+
345
+ def eager_loading?
346
+ @should_eager_load ||= (@eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?))
347
+ end
348
+
349
+ def ==(other)
350
+ case other
351
+ when Relation
352
+ other.to_sql == to_sql
353
+ when Array
354
+ to_a == other.to_a
355
+ end
356
+ end
357
+
358
+ def inspect
359
+ to_a.inspect
360
+ end
361
+
362
+ protected
363
+
364
+ def method_missing(method, *args, &block)
365
+ if Array.method_defined?(method)
366
+ to_a.send(method, *args, &block)
367
+ elsif @klass.scopes[method]
368
+ merge(@klass.send(method, *args, &block))
369
+ elsif @klass.respond_to?(method)
370
+ scoping { @klass.send(method, *args, &block) }
371
+ elsif arel.respond_to?(method)
372
+ arel.send(method, *args, &block)
373
+ else
374
+ super
375
+ end
376
+ end
377
+
378
+ private
379
+
380
+ def references_eager_loaded_tables?
381
+ # always convert table names to downcase as in Oracle quoted table names are in uppercase
382
+ joined_tables = (tables_in_string(arel.join_sql) + [table.name, table.table_alias]).compact.map{ |t| t.downcase }.uniq
383
+ (tables_in_string(to_sql) - joined_tables).any?
384
+ end
385
+
386
+ def tables_in_string(string)
387
+ return [] if string.blank?
388
+ # always convert table names to downcase as in Oracle quoted table names are in uppercase
389
+ # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
390
+ string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
391
+ end
392
+
393
+ end
394
+ end