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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1024 -543
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -29
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +55 -44
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/associations.rb +204 -276
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +30 -35
- data/lib/active_record/associations/association_scope.rb +40 -40
- data/lib/active_record/associations/belongs_to_association.rb +15 -2
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +35 -57
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +92 -88
- data/lib/active_record/associations/collection_proxy.rb +913 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
- data/lib/active_record/associations/has_many_association.rb +35 -9
- data/lib/active_record/associations/has_many_through_association.rb +24 -14
- data/lib/active_record/associations/has_one_association.rb +33 -13
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader.rb +14 -17
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +133 -153
- data/lib/active_record/attribute_methods.rb +196 -93
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +31 -28
- data/lib/active_record/attribute_methods/primary_key.rb +38 -30
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +62 -91
- data/lib/active_record/attribute_methods/serialization.rb +97 -66
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
- data/lib/active_record/attribute_methods/write.rb +32 -39
- data/lib/active_record/autosave_association.rb +56 -70
- data/lib/active_record/base.rb +53 -450
- data/lib/active_record/callbacks.rb +53 -18
- data/lib/active_record/coders/yaml_column.rb +11 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
- data/lib/active_record/connection_adapters/column.rb +46 -24
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +428 -0
- data/lib/active_record/counter_cache.rb +106 -108
- data/lib/active_record/dynamic_matchers.rb +110 -63
- data/lib/active_record/errors.rb +25 -8
- data/lib/active_record/explain.rb +8 -58
- data/lib/active_record/explain_subscriber.rb +6 -3
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +146 -148
- data/lib/active_record/inheritance.rb +77 -59
- data/lib/active_record/integration.rb +5 -5
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +38 -42
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration.rb +318 -153
- data/lib/active_record/migration/command_recorder.rb +90 -31
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +69 -92
- data/lib/active_record/nested_attributes.rb +113 -148
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +188 -97
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +91 -36
- data/lib/active_record/railties/console_sandbox.rb +0 -2
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +90 -309
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +72 -56
- data/lib/active_record/relation.rb +241 -157
- data/lib/active_record/relation/batches.rb +25 -22
- data/lib/active_record/relation/calculations.rb +143 -121
- data/lib/active_record/relation/delegation.rb +96 -18
- data/lib/active_record/relation/finder_methods.rb +117 -183
- data/lib/active_record/relation/merger.rb +133 -0
- data/lib/active_record/relation/predicate_builder.rb +90 -42
- data/lib/active_record/relation/query_methods.rb +666 -136
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/result.rb +33 -6
- data/lib/active_record/sanitization.rb +24 -50
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +31 -39
- data/lib/active_record/schema_migration.rb +36 -0
- data/lib/active_record/scoping.rb +0 -124
- data/lib/active_record/scoping/default.rb +48 -45
- data/lib/active_record/scoping/named.rb +74 -103
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/store.rb +119 -15
- data/lib/active_record/tasks/database_tasks.rb +158 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/test_case.rb +61 -38
- data/lib/active_record/timestamp.rb +8 -9
- data/lib/active_record/transactions.rb +65 -51
- data/lib/active_record/validations.rb +17 -15
- data/lib/active_record/validations/associated.rb +20 -14
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +93 -52
- data/lib/active_record/version.rb +4 -4
- data/lib/rails/generators/active_record.rb +3 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- metadata +53 -46
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/rails/generators/active_record/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -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, :
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
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
|
-
|
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, :
|
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, :
|
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, :
|
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, :
|
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, :
|
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(
|
176
|
+
def initialize(*args)
|
176
177
|
super
|
177
|
-
@collection =
|
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. +
|
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(
|
183
|
-
klass.new(
|
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
|
232
|
-
@columns ||= klass.connection.columns(tbl_name
|
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
|
270
|
-
# in the #chain.
|
271
|
-
|
272
|
-
|
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
|
317
|
+
# <tt>validate: false</tt>, validation will take place when:
|
310
318
|
#
|
311
|
-
# * you explicitly enable validation; <tt
|
312
|
-
# * you use autosave; <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, :
|
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, :
|
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, :
|
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, :
|
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
|
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
|
436
|
-
|
437
|
-
|
438
|
-
|
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
|
443
|
-
|
460
|
+
# Add to it the scope from this reflection (if any)
|
461
|
+
scope_chain.first << scope if scope
|
444
462
|
|
445
|
-
|
463
|
+
through_scope_chain = through_reflection.scope_chain
|
446
464
|
|
447
465
|
if options[:source_type]
|
448
|
-
|
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
|
-
|
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
|
-
|
9
|
-
MULTI_VALUE_METHODS
|
10
|
-
|
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 :
|
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
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
@
|
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
|
-
|
95
|
-
|
96
|
-
|
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.
|
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.
|
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.
|
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.
|
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
|
119
|
-
|
167
|
+
def find_or_create_by(attributes, &block)
|
168
|
+
find_by(attributes) || create(attributes, &block)
|
120
169
|
end
|
121
170
|
|
122
|
-
# Like <tt>
|
123
|
-
|
124
|
-
|
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>
|
130
|
-
|
131
|
-
|
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://
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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 :
|
263
|
+
# Customer.update_all wants_email: true
|
260
264
|
#
|
261
265
|
# # Update all books with 'Rails' in their title
|
262
|
-
# Book.
|
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.
|
269
|
-
|
270
|
-
|
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
|
-
|
283
|
-
stmt.table(table)
|
284
|
-
stmt.key = table[primary_key]
|
273
|
+
stmt = Arel::UpdateManager.new(arel.engine)
|
285
274
|
|
286
|
-
|
287
|
-
|
288
|
-
|
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
|
-
|
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, :
|
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.
|
308
|
+
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
|
317
309
|
else
|
318
310
|
object = find(id)
|
319
|
-
object.
|
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
|
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(:
|
350
|
-
# Person.where(:
|
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
|
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
|
387
|
-
# calling the +destroy+ method nor invoking callbacks. This
|
388
|
-
# goes straight to the database, much more
|
389
|
-
#
|
390
|
-
#
|
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(:
|
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
|
-
|
413
|
-
|
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
|
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
|
-
|
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,
|
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[
|
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
|
-
|
477
|
-
|
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. { :
|
504
|
+
# nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
|
484
505
|
def joined_includes_values
|
485
|
-
|
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
|
498
|
-
to_a
|
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
|
-
|
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)
|