activerecord 3.2.22.5 → 4.0.0.beta1

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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,6 +1,6 @@
1
1
  #FIXME Remove if ArJdbcMysql will give.
2
2
  module ArJdbcMySQL #:nodoc:
3
- class Error < StandardError
3
+ class Error < StandardError #:nodoc:
4
4
  attr_accessor :error_number, :sql_state
5
5
 
6
6
  def initialize msg
@@ -1,12 +1,10 @@
1
- require 'active_support/concern'
2
- require 'active_support/core_ext/class/attribute'
3
1
 
4
2
  module ActiveRecord
5
3
  module ReadonlyAttributes
6
4
  extend ActiveSupport::Concern
7
5
 
8
6
  included do
9
- class_attribute :_attr_readonly, :instance_writer => false
7
+ class_attribute :_attr_readonly, instance_accessor: false
10
8
  self._attr_readonly = []
11
9
  end
12
10
 
@@ -22,5 +20,11 @@ module ActiveRecord
22
20
  self._attr_readonly
23
21
  end
24
22
  end
23
+
24
+ def _attr_readonly
25
+ message = "Instance level _attr_readonly method is deprecated, please use class level method."
26
+ ActiveSupport::Deprecation.warn message
27
+ defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly
28
+ end
25
29
  end
26
30
  end
@@ -1,6 +1,3 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/object/inclusion'
3
-
4
1
  module ActiveRecord
5
2
  # = Active Record Reflection
6
3
  module Reflection # :nodoc:
@@ -20,13 +17,13 @@ module ActiveRecord
20
17
  # MacroReflection class has info for AggregateReflection and AssociationReflection
21
18
  # classes.
22
19
  module ClassMethods
23
- def create_reflection(macro, name, options, active_record)
20
+ def create_reflection(macro, name, scope, options, active_record)
24
21
  case macro
25
- when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
26
- klass = options[:through] ? ThroughReflection : AssociationReflection
27
- reflection = klass.new(macro, name, options, active_record)
28
- when :composed_of
29
- reflection = AggregateReflection.new(macro, name, options, active_record)
22
+ when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
23
+ klass = options[:through] ? ThroughReflection : AssociationReflection
24
+ reflection = klass.new(macro, name, scope, options, active_record)
25
+ when :composed_of
26
+ reflection = AggregateReflection.new(macro, name, scope, options, active_record)
30
27
  end
31
28
 
32
29
  self.reflections = self.reflections.merge(name => reflection)
@@ -43,7 +40,8 @@ module ActiveRecord
43
40
  # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
44
41
  #
45
42
  def reflect_on_aggregation(aggregation)
46
- reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
43
+ reflection = reflections[aggregation]
44
+ reflection if reflection.is_a?(AggregateReflection)
47
45
  end
48
46
 
49
47
  # Returns an array of AssociationReflection objects for all the
@@ -67,7 +65,8 @@ module ActiveRecord
67
65
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
68
66
  #
69
67
  def reflect_on_association(association)
70
- reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
68
+ reflection = reflections[association]
69
+ reflection if reflection.is_a?(AssociationReflection)
71
70
  end
72
71
 
73
72
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
@@ -76,25 +75,26 @@ module ActiveRecord
76
75
  end
77
76
  end
78
77
 
79
-
80
78
  # Abstract base class for AggregateReflection and AssociationReflection. Objects of
81
79
  # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
82
80
  class MacroReflection
83
81
  # Returns the name of the macro.
84
82
  #
85
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
83
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
86
84
  # <tt>has_many :clients</tt> returns <tt>:clients</tt>
87
85
  attr_reader :name
88
86
 
89
87
  # Returns the macro type.
90
88
  #
91
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
89
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:composed_of</tt>
92
90
  # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
93
91
  attr_reader :macro
94
92
 
93
+ attr_reader :scope
94
+
95
95
  # Returns the hash of options used for the macro.
96
96
  #
97
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
97
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
98
98
  # <tt>has_many :clients</tt> returns +{}+
99
99
  attr_reader :options
100
100
 
@@ -102,9 +102,10 @@ module ActiveRecord
102
102
 
103
103
  attr_reader :plural_name # :nodoc:
104
104
 
105
- def initialize(macro, name, options, active_record)
105
+ def initialize(macro, name, scope, options, active_record)
106
106
  @macro = macro
107
107
  @name = name
108
+ @scope = scope
108
109
  @options = options
109
110
  @active_record = active_record
110
111
  @plural_name = active_record.pluralize_table_names ?
@@ -113,7 +114,7 @@ module ActiveRecord
113
114
 
114
115
  # Returns the class for the macro.
115
116
  #
116
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
117
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
117
118
  # <tt>has_many :clients</tt> returns the Client class
118
119
  def klass
119
120
  @klass ||= class_name.constantize
@@ -121,7 +122,7 @@ module ActiveRecord
121
122
 
122
123
  # Returns the class name for the macro.
123
124
  #
124
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
125
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
125
126
  # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
126
127
  def class_name
127
128
  @class_name ||= (options[:class_name] || derive_class_name).to_s
@@ -137,10 +138,6 @@ module ActiveRecord
137
138
  active_record == other_aggregation.active_record
138
139
  end
139
140
 
140
- def sanitized_conditions #:nodoc:
141
- @sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
142
- end
143
-
144
141
  private
145
142
  def derive_class_name
146
143
  name.to_s.camelize
@@ -151,6 +148,10 @@ module ActiveRecord
151
148
  # Holds all the meta-data about an aggregation as it was specified in the
152
149
  # Active Record class.
153
150
  class AggregateReflection < MacroReflection #:nodoc:
151
+ def mapping
152
+ mapping = options[:mapping] || [name, name]
153
+ mapping.first.is_a?(Array) ? mapping : [mapping]
154
+ end
154
155
  end
155
156
 
156
157
  # Holds all the meta-data about an association as it was specified in the
@@ -172,15 +173,15 @@ module ActiveRecord
172
173
  @klass ||= active_record.send(:compute_type, class_name)
173
174
  end
174
175
 
175
- def initialize(macro, name, options, active_record)
176
+ def initialize(*args)
176
177
  super
177
- @collection = macro.in?([:has_many, :has_and_belongs_to_many])
178
+ @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
178
179
  end
179
180
 
180
- # Returns a new, unsaved instance of the associated class. +options+ will
181
+ # Returns a new, unsaved instance of the associated class. +attributes+ will
181
182
  # be passed to the class's constructor.
182
- def build_association(*options, &block)
183
- klass.new(*options, &block)
183
+ def build_association(attributes, &block)
184
+ klass.new(attributes, &block)
184
185
  end
185
186
 
186
187
  def table_name
@@ -191,6 +192,10 @@ module ActiveRecord
191
192
  @quoted_table_name ||= klass.quoted_table_name
192
193
  end
193
194
 
195
+ def join_table
196
+ @join_table ||= options[:join_table] || derive_join_table
197
+ end
198
+
194
199
  def foreign_key
195
200
  @foreign_key ||= options[:foreign_key] || derive_foreign_key
196
201
  end
@@ -228,8 +233,8 @@ module ActiveRecord
228
233
  end
229
234
  end
230
235
 
231
- def columns(tbl_name, log_msg)
232
- @columns ||= klass.connection.columns(tbl_name, log_msg)
236
+ def columns(tbl_name)
237
+ @columns ||= klass.connection.columns(tbl_name)
233
238
  end
234
239
 
235
240
  def reset_column_information
@@ -238,6 +243,10 @@ module ActiveRecord
238
243
 
239
244
  def check_validity!
240
245
  check_validity_of_inverse!
246
+
247
+ if has_and_belongs_to_many? && association_foreign_key == foreign_key
248
+ raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
249
+ end
241
250
  end
242
251
 
243
252
  def check_validity_of_inverse!
@@ -266,11 +275,10 @@ module ActiveRecord
266
275
  false
267
276
  end
268
277
 
269
- # An array of arrays of conditions. Each item in the outside array corresponds to a reflection
270
- # in the #chain. The inside arrays are simply conditions (and each condition may itself be
271
- # a hash, array, arel predicate, etc...)
272
- def conditions
273
- [[options[:conditions]].compact]
278
+ # An array of arrays of scopes. Each item in the outside array corresponds to a reflection
279
+ # in the #chain.
280
+ def scope_chain
281
+ scope ? [[scope]] : [[]]
274
282
  end
275
283
 
276
284
  alias :source_macro :macro
@@ -306,10 +314,10 @@ module ActiveRecord
306
314
  # the parent's validation.
307
315
  #
308
316
  # Unless you explicitly disable validation with
309
- # <tt>:validate => false</tt>, validation will take place when:
317
+ # <tt>validate: false</tt>, validation will take place when:
310
318
  #
311
- # * you explicitly enable validation; <tt>:validate => true</tt>
312
- # * you use autosave; <tt>:autosave => true</tt>
319
+ # * you explicitly enable validation; <tt>validate: true</tt>
320
+ # * you use autosave; <tt>autosave: true</tt>
313
321
  # * the association is a +has_many+ association
314
322
  def validate?
315
323
  !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
@@ -320,6 +328,10 @@ module ActiveRecord
320
328
  macro == :belongs_to
321
329
  end
322
330
 
331
+ def has_and_belongs_to_many?
332
+ macro == :has_and_belongs_to_many
333
+ end
334
+
323
335
  def association_class
324
336
  case macro
325
337
  when :belongs_to
@@ -345,6 +357,10 @@ module ActiveRecord
345
357
  end
346
358
  end
347
359
 
360
+ def polymorphic?
361
+ options.key? :polymorphic
362
+ end
363
+
348
364
  private
349
365
  def derive_class_name
350
366
  class_name = name.to_s.camelize
@@ -362,6 +378,10 @@ module ActiveRecord
362
378
  end
363
379
  end
364
380
 
381
+ def derive_join_table
382
+ [active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
383
+ end
384
+
365
385
  def primary_key(klass)
366
386
  klass.primary_key || raise(UnknownPrimaryKey.new(klass))
367
387
  end
@@ -378,7 +398,7 @@ module ActiveRecord
378
398
  #
379
399
  # class Post < ActiveRecord::Base
380
400
  # has_many :taggings
381
- # has_many :tags, :through => :taggings
401
+ # has_many :tags, through: :taggings
382
402
  # end
383
403
  #
384
404
  def source_reflection
@@ -390,7 +410,7 @@ module ActiveRecord
390
410
  #
391
411
  # class Post < ActiveRecord::Base
392
412
  # has_many :taggings
393
- # has_many :tags, :through => :taggings
413
+ # has_many :tags, through: :taggings
394
414
  # end
395
415
  #
396
416
  # tags_reflection = Post.reflect_on_association(:tags)
@@ -418,41 +438,37 @@ module ActiveRecord
418
438
  #
419
439
  # class Person
420
440
  # has_many :articles
421
- # has_many :comment_tags, :through => :articles
441
+ # has_many :comment_tags, through: :articles
422
442
  # end
423
443
  #
424
444
  # class Article
425
445
  # has_many :comments
426
- # has_many :comment_tags, :through => :comments, :source => :tags
446
+ # has_many :comment_tags, through: :comments, source: :tags
427
447
  # end
428
448
  #
429
449
  # class Comment
430
450
  # has_many :tags
431
451
  # end
432
452
  #
433
- # There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
453
+ # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
434
454
  # but only Comment.tags will be represented in the #chain. So this method creates an array
435
- # of conditions corresponding to the chain. Each item in the #conditions array corresponds
436
- # to an item in the #chain, and is itself an array of conditions from an arbitrary number
437
- # of relevant reflections, plus any :source_type or polymorphic :as constraints.
438
- def conditions
439
- @conditions ||= begin
440
- conditions = source_reflection.conditions.map { |c| c.dup }
455
+ # of scopes corresponding to the chain.
456
+ def scope_chain
457
+ @scope_chain ||= begin
458
+ scope_chain = source_reflection.scope_chain.map(&:dup)
441
459
 
442
- # Add to it the conditions from this reflection if necessary.
443
- conditions.first << options[:conditions] if options[:conditions]
460
+ # Add to it the scope from this reflection (if any)
461
+ scope_chain.first << scope if scope
444
462
 
445
- through_conditions = through_reflection.conditions
463
+ through_scope_chain = through_reflection.scope_chain
446
464
 
447
465
  if options[:source_type]
448
- through_conditions.first << { foreign_type => options[:source_type] }
466
+ through_scope_chain.first <<
467
+ through_reflection.klass.where(foreign_type => options[:source_type])
449
468
  end
450
469
 
451
470
  # Recursively fill out the rest of the array from the through reflection
452
- conditions += through_conditions
453
-
454
- # And return
455
- conditions
471
+ scope_chain + through_scope_chain
456
472
  end
457
473
  end
458
474
 
@@ -1,32 +1,42 @@
1
1
  # -*- coding: utf-8 -*-
2
- require 'active_support/core_ext/object/blank'
3
2
 
4
3
  module ActiveRecord
5
4
  # = Active Record Relation
6
5
  class Relation
7
6
  JoinOperation = Struct.new(:relation, :join_class, :on)
8
- ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
9
- MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind]
10
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq]
7
+
8
+ MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
9
+ :order, :joins, :where, :having, :bind, :references,
10
+ :extending]
11
+
12
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
13
+ :reverse_order, :uniq, :create_with]
14
+
15
+ VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
11
16
 
12
17
  include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
13
18
 
14
19
  attr_reader :table, :klass, :loaded
15
- attr_accessor :extensions, :default_scoped
20
+ attr_accessor :default_scoped
21
+ alias :model :klass
16
22
  alias :loaded? :loaded
17
23
  alias :default_scoped? :default_scoped
18
24
 
19
- def initialize(klass, table)
20
- @klass, @table = klass, table
21
-
25
+ def initialize(klass, table, values = {})
26
+ @klass = klass
27
+ @table = table
28
+ @values = values
22
29
  @implicit_readonly = nil
23
30
  @loaded = false
24
31
  @default_scoped = false
32
+ end
25
33
 
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
- @create_with_value = {}
34
+ def initialize_copy(other)
35
+ # This method is a hot spot, so for now, use Hash[] to dup the hash.
36
+ # https://bugs.ruby-lang.org/issues/7166
37
+ @values = Hash[@values]
38
+ @values[:bind] = @values[:bind].dup if @values.key? :bind
39
+ reset
30
40
  end
31
41
 
32
42
  def insert(values)
@@ -72,65 +82,100 @@ module ActiveRecord
72
82
  binds)
73
83
  end
74
84
 
85
+ # Initializes new record from relation while maintaining the current
86
+ # scope.
87
+ #
88
+ # Expects arguments in the same format as +Base.new+.
89
+ #
90
+ # users = User.where(name: 'DHH')
91
+ # user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
92
+ #
93
+ # You can also pass a block to new with the new record as argument:
94
+ #
95
+ # user = users.new { |user| user.name = 'Oscar' }
96
+ # user.name # => Oscar
75
97
  def new(*args, &block)
76
98
  scoping { @klass.new(*args, &block) }
77
99
  end
78
100
 
79
- def initialize_copy(other)
80
- @bind_values = @bind_values.dup
81
- reset
82
- end
83
-
84
101
  alias build new
85
102
 
103
+ # Tries to create a new record with the same scoped attributes
104
+ # defined in the relation. Returns the initialized object if validation fails.
105
+ #
106
+ # Expects arguments in the same format as +Base.create+.
107
+ #
108
+ # ==== Examples
109
+ # users = User.where(name: 'Oscar')
110
+ # users.create # #<User id: 3, name: "oscar", ...>
111
+ #
112
+ # users.create(name: 'fxn')
113
+ # users.create # #<User id: 4, name: "fxn", ...>
114
+ #
115
+ # users.create { |user| user.name = 'tenderlove' }
116
+ # # #<User id: 5, name: "tenderlove", ...>
117
+ #
118
+ # users.create(name: nil) # validation on name
119
+ # # #<User id: nil, name: nil, ...>
86
120
  def create(*args, &block)
87
121
  scoping { @klass.create(*args, &block) }
88
122
  end
89
123
 
124
+ # Similar to #create, but calls +create!+ on the base class. Raises
125
+ # an exception if a validation error occurs.
126
+ #
127
+ # Expects arguments in the same format as <tt>Base.create!</tt>.
90
128
  def create!(*args, &block)
91
129
  scoping { @klass.create!(*args, &block) }
92
130
  end
93
131
 
94
- # Tries to load the first record; if it fails, then <tt>create</tt> is called with the same arguments as this method.
95
- #
96
- # Expects arguments in the same format as <tt>Base.create</tt>.
132
+ def first_or_create(attributes = nil, &block) # :nodoc:
133
+ first || create(attributes, &block)
134
+ end
135
+
136
+ def first_or_create!(attributes = nil, &block) # :nodoc:
137
+ first || create!(attributes, &block)
138
+ end
139
+
140
+ def first_or_initialize(attributes = nil, &block) # :nodoc:
141
+ first || new(attributes, &block)
142
+ end
143
+
144
+ # Finds the first record with the given attributes, or creates a record with the attributes
145
+ # if one is not found.
97
146
  #
98
147
  # ==== Examples
99
148
  # # Find the first user named Penélope or create a new one.
100
- # User.where(:first_name => 'Penélope').first_or_create
149
+ # User.find_or_create_by(first_name: 'Penélope')
101
150
  # # => <User id: 1, first_name: 'Penélope', last_name: nil>
102
151
  #
103
152
  # # Find the first user named Penélope or create a new one.
104
153
  # # We already have one so the existing record will be returned.
105
- # User.where(:first_name => 'Penélope').first_or_create
154
+ # User.find_or_create_by(first_name: 'Penélope')
106
155
  # # => <User id: 1, first_name: 'Penélope', last_name: nil>
107
156
  #
108
157
  # # Find the first user named Scarlett or create a new one with a particular last name.
109
- # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')
158
+ # User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
110
159
  # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
111
160
  #
112
161
  # # Find the first user named Scarlett or create a new one with a different last name.
113
162
  # # We already have one so the existing record will be returned.
114
- # User.where(:first_name => 'Scarlett').first_or_create do |user|
163
+ # User.find_or_create_by(first_name: 'Scarlett') do |user|
115
164
  # user.last_name = "O'Hara"
116
165
  # end
117
166
  # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
118
- def first_or_create(attributes = nil, options = {}, &block)
119
- first || create(attributes, options, &block)
167
+ def find_or_create_by(attributes, &block)
168
+ find_by(attributes) || create(attributes, &block)
120
169
  end
121
170
 
122
- # Like <tt>first_or_create</tt> but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
123
- #
124
- # Expects arguments in the same format as <tt>Base.create!</tt>.
125
- def first_or_create!(attributes = nil, options = {}, &block)
126
- first || create!(attributes, options, &block)
171
+ # Like <tt>find_or_create_by</tt>, but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
172
+ def find_or_create_by!(attributes, &block)
173
+ find_by(attributes) || create!(attributes, &block)
127
174
  end
128
175
 
129
- # Like <tt>first_or_create</tt> but calls <tt>new</tt> instead of <tt>create</tt>.
130
- #
131
- # Expects arguments in the same format as <tt>Base.new</tt>.
132
- def first_or_initialize(attributes = nil, options = {}, &block)
133
- first || new(attributes, options, &block)
176
+ # Like <tt>find_or_create_by</tt>, but calls <tt>new</tt> instead of <tt>create</tt>.
177
+ def find_or_initialize_by(attributes, &block)
178
+ find_by(attributes) || new(attributes, &block)
134
179
  end
135
180
 
136
181
  # Runs EXPLAIN on the query or queries triggered by this relation and
@@ -141,58 +186,17 @@ module ActiveRecord
141
186
  # are needed by the next ones when eager loading is going on.
142
187
  #
143
188
  # Please see further details in the
144
- # {Active Record Query Interface guide}[http://edgeguides.rubyonrails.org/active_record_querying.html#running-explain].
189
+ # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
145
190
  def explain
146
191
  _, queries = collecting_queries_for_explain { exec_queries }
147
192
  exec_explain(queries)
148
193
  end
149
194
 
195
+ # Converts relation objects to Array.
150
196
  def to_a
151
- # We monitor here the entire execution rather than individual SELECTs
152
- # because from the point of view of the user fetching the records of a
153
- # relation is a single unit of work. You want to know if this call takes
154
- # too long, not if the individual queries take too long.
155
- #
156
- # It could be the case that none of the queries involved surpass the
157
- # threshold, and at the same time the sum of them all does. The user
158
- # should get a query plan logged in that case.
159
- logging_query_plan do
160
- exec_queries
161
- end
162
- end
163
-
164
- def exec_queries
165
- return @records if loaded?
166
-
167
- default_scoped = with_default_scope
168
-
169
- if default_scoped.equal?(self)
170
- @records = if @readonly_value.nil? && !@klass.locking_enabled?
171
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
172
- else
173
- IdentityMap.without do
174
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
175
- end
176
- end
177
-
178
- preload = @preload_values
179
- preload += @includes_values unless eager_loading?
180
- preload.each do |associations|
181
- ActiveRecord::Associations::Preloader.new(@records, associations).run
182
- end
183
-
184
- # @readonly_value is true only if set explicitly. @implicit_readonly is true if there
185
- # are JOINS and no explicit SELECT.
186
- readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
187
- @records.each { |record| record.readonly! } if readonly
188
- else
189
- @records = default_scoped.to_a
190
- end
191
-
192
- @loaded = true
197
+ load
193
198
  @records
194
199
  end
195
- private :exec_queries
196
200
 
197
201
  def as_json(options = nil) #:nodoc:
198
202
  to_a.as_json(options)
@@ -211,6 +215,7 @@ module ActiveRecord
211
215
  c.respond_to?(:zero?) ? c.zero? : c.empty?
212
216
  end
213
217
 
218
+ # Returns true if there are any records.
214
219
  def any?
215
220
  if block_given?
216
221
  to_a.any? { |*block_args| yield(*block_args) }
@@ -219,26 +224,28 @@ module ActiveRecord
219
224
  end
220
225
  end
221
226
 
227
+ # Returns true if there is more than one record.
222
228
  def many?
223
229
  if block_given?
224
230
  to_a.many? { |*block_args| yield(*block_args) }
225
231
  else
226
- @limit_value ? to_a.many? : size > 1
232
+ limit_value ? to_a.many? : size > 1
227
233
  end
228
234
  end
229
235
 
230
236
  # Scope all queries to the current scope.
231
237
  #
232
- # ==== Example
233
- #
234
- # Comment.where(:post_id => 1).scoping do
238
+ # Comment.where(post_id: 1).scoping do
235
239
  # Comment.first # SELECT * FROM comments WHERE post_id = 1
236
240
  # end
237
241
  #
238
242
  # Please check unscoped if you want to remove all previous scopes (including
239
243
  # the default_scope) during the execution of a block.
240
244
  def scoping
241
- @klass.with_scope(self, :overwrite) { yield }
245
+ previous, klass.current_scope = klass.current_scope, self
246
+ yield
247
+ ensure
248
+ klass.current_scope = previous
242
249
  end
243
250
 
244
251
  # Updates all records with details given if they match a set of conditions supplied, limits and order can
@@ -249,50 +256,35 @@ module ActiveRecord
249
256
  # ==== Parameters
250
257
  #
251
258
  # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
252
- # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement.
253
- # See conditions in the intro.
254
- # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
255
259
  #
256
260
  # ==== Examples
257
261
  #
258
262
  # # Update all customers with the given attributes
259
- # Customer.update_all :wants_email => true
263
+ # Customer.update_all wants_email: true
260
264
  #
261
265
  # # Update all books with 'Rails' in their title
262
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'"
263
- #
264
- # # Update all avatars migrated more than a week ago
265
- # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
266
+ # Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
266
267
  #
267
268
  # # Update all books that match conditions, but limit it to 5 ordered by date
268
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
269
- #
270
- # # Conditions from the current relation also works
271
- # Book.where('title LIKE ?', '%Rails%').update_all(:author => 'David')
272
- #
273
- # # The same idea applies to limit and order
274
- # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David')
275
- def update_all(updates, conditions = nil, options = {})
276
- IdentityMap.repository[symbolized_base_class].clear if IdentityMap.enabled?
277
- if conditions || options.present?
278
- where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
279
- else
280
- stmt = Arel::UpdateManager.new(arel.engine)
269
+ # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
270
+ def update_all(updates)
271
+ raise ArgumentError, "Empty list of attributes to change" if updates.blank?
281
272
 
282
- stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
283
- stmt.table(table)
284
- stmt.key = table[primary_key]
273
+ stmt = Arel::UpdateManager.new(arel.engine)
285
274
 
286
- if joins_values.any?
287
- @klass.connection.join_to_update(stmt, arel)
288
- else
289
- stmt.take(arel.limit)
290
- stmt.order(*arel.orders)
291
- stmt.wheres = arel.constraints
292
- end
275
+ stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
276
+ stmt.table(table)
277
+ stmt.key = table[primary_key]
293
278
 
294
- @klass.connection.update stmt, 'SQL', bind_values
279
+ if with_default_scope.joins_values.any?
280
+ @klass.connection.join_to_update(stmt, arel)
281
+ else
282
+ stmt.take(arel.limit)
283
+ stmt.order(*arel.orders)
284
+ stmt.wheres = arel.constraints
295
285
  end
286
+
287
+ @klass.connection.update stmt, 'SQL', bind_values
296
288
  end
297
289
 
298
290
  # Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -306,28 +298,26 @@ module ActiveRecord
306
298
  # ==== Examples
307
299
  #
308
300
  # # Updates one record
309
- # Person.update(15, :user_name => 'Samuel', :group => 'expert')
301
+ # Person.update(15, user_name: 'Samuel', group: 'expert')
310
302
  #
311
303
  # # Updates multiple records
312
304
  # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
313
305
  # Person.update(people.keys, people.values)
314
306
  def update(id, attributes)
315
307
  if id.is_a?(Array)
316
- id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])}
308
+ id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
317
309
  else
318
310
  object = find(id)
319
- object.update_attributes(attributes)
311
+ object.update(attributes)
320
312
  object
321
313
  end
322
314
  end
323
315
 
324
316
  # Destroys the records matching +conditions+ by instantiating each
325
317
  # record and calling its +destroy+ method. Each object's callbacks are
326
- # executed (including <tt>:dependent</tt> association options and
327
- # +before_destroy+/+after_destroy+ Observer methods). Returns the
318
+ # executed (including <tt>:dependent</tt> association options). Returns the
328
319
  # collection of objects that were destroyed; each will be frozen, to
329
- # reflect that no changes should be made (since they can't be
330
- # persisted).
320
+ # reflect that no changes should be made (since they can't be persisted).
331
321
  #
332
322
  # Note: Instantiation, callback execution, and deletion of each
333
323
  # record can be time consuming when you're removing many records at
@@ -346,8 +336,8 @@ module ActiveRecord
346
336
  # ==== Examples
347
337
  #
348
338
  # Person.destroy_all("last_login < '2004-04-04'")
349
- # Person.destroy_all(:status => "inactive")
350
- # Person.where(:age => 0..18).destroy_all
339
+ # Person.destroy_all(status: "inactive")
340
+ # Person.where(age: 0..18).destroy_all
351
341
  def destroy_all(conditions = nil)
352
342
  if conditions
353
343
  where(conditions).destroy_all
@@ -356,7 +346,7 @@ module ActiveRecord
356
346
  end
357
347
  end
358
348
 
359
- # Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
349
+ # Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
360
350
  # therefore all callbacks and filters are fired off before the object is deleted. This method is
361
351
  # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
362
352
  #
@@ -383,34 +373,41 @@ module ActiveRecord
383
373
  end
384
374
  end
385
375
 
386
- # Deletes the records matching +conditions+ without instantiating the records first, and hence not
387
- # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
388
- # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
389
- # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
390
- # the number of rows affected.
391
- #
392
- # ==== Parameters
393
- #
394
- # * +conditions+ - Conditions are specified the same way as with +find+ method.
395
- #
396
- # ==== Example
376
+ # Deletes the records matching +conditions+ without instantiating the records
377
+ # first, and hence not calling the +destroy+ method nor invoking callbacks. This
378
+ # is a single SQL DELETE statement that goes straight to the database, much more
379
+ # efficient than +destroy_all+. Be careful with relations though, in particular
380
+ # <tt>:dependent</tt> rules defined on associations are not honored. Returns the
381
+ # number of rows affected.
397
382
  #
398
383
  # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
399
384
  # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
400
- # Post.where(:person_id => 5).where(:category => ['Something', 'Else']).delete_all
385
+ # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
401
386
  #
402
387
  # Both calls delete the affected posts all at once with a single DELETE statement.
403
388
  # If you need to destroy dependent associations or call your <tt>before_*</tt> or
404
389
  # +after_destroy+ callbacks, use the +destroy_all+ method instead.
390
+ #
391
+ # If a limit scope is supplied, +delete_all+ raises an ActiveRecord error:
392
+ #
393
+ # Post.limit(100).delete_all
394
+ # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit scope
405
395
  def delete_all(conditions = nil)
406
396
  raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
407
397
 
408
- IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled?
409
398
  if conditions
410
399
  where(conditions).delete_all
411
400
  else
412
- statement = arel.compile_delete
413
- affected = @klass.connection.delete(statement, 'SQL', bind_values)
401
+ stmt = Arel::DeleteManager.new(arel.engine)
402
+ stmt.from(table)
403
+
404
+ if with_default_scope.joins_values.any?
405
+ @klass.connection.join_to_delete(stmt, arel, table[primary_key])
406
+ else
407
+ stmt.wheres = arel.constraints
408
+ end
409
+
410
+ affected = @klass.connection.delete(stmt, 'SQL', bind_values)
414
411
 
415
412
  reset
416
413
  affected
@@ -420,8 +417,7 @@ module ActiveRecord
420
417
  # Deletes the row with a primary key matching the +id+ argument, using a
421
418
  # SQL +DELETE+ statement, and returns the number of rows deleted. Active
422
419
  # Record objects are not instantiated, so the object's callbacks are not
423
- # executed, including any <tt>:dependent</tt> association options or
424
- # Observer methods.
420
+ # executed, including any <tt>:dependent</tt> association options.
425
421
  #
426
422
  # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
427
423
  #
@@ -438,14 +434,25 @@ module ActiveRecord
438
434
  # # Delete multiple rows
439
435
  # Todo.delete([2,3,4])
440
436
  def delete(id_or_array)
441
- IdentityMap.remove_by_id(self.symbolized_base_class, id_or_array) if IdentityMap.enabled?
442
437
  where(primary_key => id_or_array).delete_all
443
438
  end
444
439
 
440
+ # Causes the records to be loaded from the database if they have not
441
+ # been loaded already. You can use this if for some reason you need
442
+ # to explicitly load some records before actually using them. The
443
+ # return value is the relation itself, not the records.
444
+ #
445
+ # Post.where(published: true).load # => #<ActiveRecord::Relation>
446
+ def load
447
+ exec_queries unless loaded?
448
+
449
+ self
450
+ end
451
+
452
+ # Forces reloading of relation.
445
453
  def reload
446
454
  reset
447
- to_a # force reload
448
- self
455
+ load
449
456
  end
450
457
 
451
458
  def reset
@@ -455,36 +462,51 @@ module ActiveRecord
455
462
  self
456
463
  end
457
464
 
465
+ # Returns sql statement for the relation.
466
+ #
467
+ # User.where(name: 'Oscar').to_sql
468
+ # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
458
469
  def to_sql
459
- @to_sql ||= klass.connection.to_sql(arel, @bind_values.dup)
470
+ @to_sql ||= klass.connection.to_sql(arel, bind_values.dup)
460
471
  end
461
472
 
473
+ # Returns a hash of where conditions.
474
+ #
475
+ # User.where(name: 'Oscar').where_values_hash
476
+ # # => {name: "Oscar"}
462
477
  def where_values_hash
463
478
  equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
464
479
  node.left.relation.name == table_name
465
480
  }
466
481
 
467
- Hash[equalities.map { |where| [where.left.name, where.right] }].with_indifferent_access
482
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
483
+
484
+ Hash[equalities.map { |where|
485
+ name = where.left.name
486
+ [name, binds.fetch(name.to_s) { where.right }]
487
+ }]
468
488
  end
469
489
 
470
490
  def scope_for_create
471
491
  @scope_for_create ||= where_values_hash.merge(create_with_value)
472
492
  end
473
493
 
494
+ # Returns true if relation needs eager loading.
474
495
  def eager_loading?
475
496
  @should_eager_load ||=
476
- @eager_load_values.any? ||
477
- @includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
497
+ eager_load_values.any? ||
498
+ includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
478
499
  end
479
500
 
480
501
  # Joins that are also marked for preloading. In which case we should just eager load them.
481
502
  # Note that this is a naive implementation because we could have strings and symbols which
482
503
  # represent the same association, but that aren't matched by this. Also, we could have
483
- # nested hashes which partially match, e.g. { :a => :b } & { :a => [:b, :c] }
504
+ # nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
484
505
  def joined_includes_values
485
- @includes_values & @joins_values
506
+ includes_values & joins_values
486
507
  end
487
508
 
509
+ # Compares two relations for equality.
488
510
  def ==(other)
489
511
  case other
490
512
  when Relation
@@ -494,8 +516,8 @@ module ActiveRecord
494
516
  end
495
517
  end
496
518
 
497
- def inspect
498
- to_a.inspect
519
+ def pretty_print(q)
520
+ q.pp(self.to_a)
499
521
  end
500
522
 
501
523
  def with_default_scope #:nodoc:
@@ -508,8 +530,48 @@ module ActiveRecord
508
530
  end
509
531
  end
510
532
 
533
+ # Returns true if relation is blank.
534
+ def blank?
535
+ to_a.blank?
536
+ end
537
+
538
+ def values
539
+ Hash[@values]
540
+ end
541
+
542
+ def inspect
543
+ entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
544
+ entries[10] = '...' if entries.size == 11
545
+
546
+ "#<#{self.class.name} [#{entries.join(', ')}]>"
547
+ end
548
+
511
549
  private
512
550
 
551
+ def exec_queries
552
+ default_scoped = with_default_scope
553
+
554
+ if default_scoped.equal?(self)
555
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
556
+
557
+ preload = preload_values
558
+ preload += includes_values unless eager_loading?
559
+ preload.each do |associations|
560
+ ActiveRecord::Associations::Preloader.new(@records, associations).run
561
+ end
562
+
563
+ # @readonly_value is true only if set explicitly. @implicit_readonly is true if there
564
+ # are JOINS and no explicit SELECT.
565
+ readonly = readonly_value.nil? ? @implicit_readonly : readonly_value
566
+ @records.each { |record| record.readonly! } if readonly
567
+ else
568
+ @records = default_scoped.to_a
569
+ end
570
+
571
+ @loaded = true
572
+ @records
573
+ end
574
+
513
575
  def references_eager_loaded_tables?
514
576
  joined_tables = arel.join_sources.map do |join|
515
577
  if join.is_a?(Arel::Nodes::StringJoin)
@@ -523,8 +585,30 @@ module ActiveRecord
523
585
 
524
586
  # always convert table names to downcase as in Oracle quoted table names are in uppercase
525
587
  joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
526
-
527
- (tables_in_string(to_sql) - joined_tables).any?
588
+ string_tables = tables_in_string(to_sql)
589
+
590
+ if (references_values - joined_tables).any?
591
+ true
592
+ elsif (string_tables - joined_tables).any?
593
+ ActiveSupport::Deprecation.warn(
594
+ "It looks like you are eager loading table(s) (one of: #{string_tables.join(', ')}) " \
595
+ "that are referenced in a string SQL snippet. For example: \n" \
596
+ "\n" \
597
+ " Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \
598
+ "\n" \
599
+ "Currently, Active Record recognises the table in the string, and knows to JOIN the " \
600
+ "comments table to the query, rather than loading comments in a separate query. " \
601
+ "However, doing this without writing a full-blown SQL parser is inherently flawed. " \
602
+ "Since we don't want to write an SQL parser, we are removing this functionality. " \
603
+ "From now on, you must explicitly tell Active Record when you are referencing a table " \
604
+ "from a string:\n" \
605
+ "\n" \
606
+ " Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n\n"
607
+ )
608
+ true
609
+ else
610
+ false
611
+ end
528
612
  end
529
613
 
530
614
  def tables_in_string(string)