activerecord 3.1.0.rc5 → 3.1.0.rc6

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 (34) hide show
  1. data/CHANGELOG +6 -0
  2. data/README.rdoc +1 -1
  3. data/lib/active_record/associations.rb +6 -0
  4. data/lib/active_record/associations/association.rb +8 -8
  5. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -5
  6. data/lib/active_record/associations/collection_association.rb +24 -43
  7. data/lib/active_record/associations/collection_proxy.rb +19 -12
  8. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +2 -2
  9. data/lib/active_record/associations/join_dependency.rb +2 -3
  10. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
  11. data/lib/active_record/attribute_methods/primary_key.rb +2 -3
  12. data/lib/active_record/base.rb +36 -44
  13. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +15 -4
  14. data/lib/active_record/connection_adapters/abstract/database_statements.rb +33 -14
  15. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -2
  16. data/lib/active_record/connection_adapters/abstract_adapter.rb +22 -0
  17. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -1
  18. data/lib/active_record/connection_adapters/mysql_adapter.rb +26 -1
  19. data/lib/active_record/connection_adapters/postgresql_adapter.rb +4 -0
  20. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -0
  21. data/lib/active_record/connection_adapters/sqlite_adapter.rb +5 -1
  22. data/lib/active_record/counter_cache.rb +1 -1
  23. data/lib/active_record/locking/optimistic.rb +2 -2
  24. data/lib/active_record/migration.rb +7 -3
  25. data/lib/active_record/observer.rb +1 -1
  26. data/lib/active_record/persistence.rb +1 -1
  27. data/lib/active_record/railties/databases.rake +2 -1
  28. data/lib/active_record/relation.rb +17 -12
  29. data/lib/active_record/relation/calculations.rb +2 -2
  30. data/lib/active_record/relation/finder_methods.rb +2 -2
  31. data/lib/active_record/schema_dumper.rb +4 -0
  32. data/lib/active_record/validations/associated.rb +1 -9
  33. data/lib/active_record/version.rb +1 -1
  34. metadata +27 -13
data/CHANGELOG CHANGED
@@ -1,5 +1,11 @@
1
1
  *Rails 3.1.0 (unreleased)*
2
2
 
3
+ * Add a proxy_association method to association proxies, which can be called by association
4
+ extensions to access information about the association. This replaces proxy_owner etc with
5
+ proxy_association.owner.
6
+
7
+ [Jon Leighton]
8
+
3
9
  * Active Record's dynamic finder will now show a deprecation warning if you passing in less number of arguments than what you call in method signature. This behavior will raise ArgumentError in the next version of Rails [Prem Sichanugrist]
4
10
 
5
11
  * Deprecated the AssociationCollection constant. CollectionProxy is now the appropriate constant
@@ -203,7 +203,7 @@ The latest version of Active Record can be installed with Rubygems:
203
203
 
204
204
  Source code can be downloaded as part of the Rails project on GitHub
205
205
 
206
- * https://github.com/rails/rails/tree/master/activerecord/
206
+ * https://github.com/rails/rails/tree/master/activerecord
207
207
 
208
208
 
209
209
  == License
@@ -467,6 +467,12 @@ module ActiveRecord
467
467
  # * <tt>record.association(:items).target</tt> - Returns the associated object for +belongs_to+ and +has_one+, or
468
468
  # the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
469
469
  #
470
+ # However, inside the actual extension code, you will not have access to the <tt>record</tt> as
471
+ # above. In this case, you can access <tt>proxy_association</tt>. For example,
472
+ # <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return
473
+ # the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside
474
+ # association extensions.
475
+ #
470
476
  # === Association Join Models
471
477
  #
472
478
  # Has Many associations can be configured with the <tt>:through</tt> option to use an
@@ -152,20 +152,20 @@ module ActiveRecord
152
152
  reset
153
153
  end
154
154
 
155
+ def interpolate(sql, record = nil)
156
+ if sql.respond_to?(:to_proc)
157
+ owner.send(:instance_exec, record, &sql)
158
+ else
159
+ sql
160
+ end
161
+ end
162
+
155
163
  private
156
164
 
157
165
  def find_target?
158
166
  !loaded? && (!owner.new_record? || foreign_key_present?) && klass
159
167
  end
160
168
 
161
- def interpolate(sql, record = nil)
162
- if sql.respond_to?(:to_proc)
163
- owner.send(:instance_exec, record, &sql)
164
- else
165
- sql
166
- end
167
- end
168
-
169
169
  def creation_attributes
170
170
  attributes = {}
171
171
 
@@ -2,6 +2,11 @@ module ActiveRecord
2
2
  # = Active Record Belongs To Polymorphic Association
3
3
  module Associations
4
4
  class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
5
+ def klass
6
+ type = owner[reflection.foreign_type]
7
+ type.presence && type.constantize
8
+ end
9
+
5
10
  private
6
11
 
7
12
  def replace_keys(record)
@@ -17,11 +22,6 @@ module ActiveRecord
17
22
  reflection.polymorphic_inverse_of(record.class)
18
23
  end
19
24
 
20
- def klass
21
- type = owner[reflection.foreign_type]
22
- type.presence && type.constantize
23
- end
24
-
25
25
  def raise_on_type_mismatch(record)
26
26
  # A polymorphic association cannot have a type mismatch, by definition
27
27
  end
@@ -114,13 +114,19 @@ module ActiveRecord
114
114
  # Add +records+ to this association. Returns +self+ so method calls may be chained.
115
115
  # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
116
116
  def concat(*records)
117
+ result = true
117
118
  load_target if owner.new_record?
118
119
 
119
- if owner.new_record?
120
- concat_records(records)
121
- else
122
- transaction { concat_records(records) }
120
+ transaction do
121
+ records.flatten.each do |record|
122
+ raise_on_type_mismatch(record)
123
+ add_to_target(record) do |r|
124
+ result &&= insert_record(record) unless owner.new_record?
125
+ end
126
+ end
123
127
  end
128
+
129
+ result && records
124
130
  end
125
131
 
126
132
  # Starts a transaction in the association class's database connection.
@@ -289,10 +295,14 @@ module ActiveRecord
289
295
  other_array.each { |val| raise_on_type_mismatch(val) }
290
296
  original_target = load_target.dup
291
297
 
292
- if owner.new_record?
293
- replace_records(other_array, original_target)
294
- else
295
- transaction { replace_records(other_array, original_target) }
298
+ transaction do
299
+ delete(target - other_array)
300
+
301
+ unless concat(other_array - target)
302
+ @target = original_target
303
+ raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
304
+ "new records could not be saved."
305
+ end
296
306
  end
297
307
  end
298
308
 
@@ -428,20 +438,14 @@ module ActiveRecord
428
438
  records.each { |record| raise_on_type_mismatch(record) }
429
439
  existing_records = records.reject { |r| r.new_record? }
430
440
 
431
- if existing_records.empty?
432
- remove_records(existing_records, records, method)
433
- else
434
- transaction { remove_records(existing_records, records, method) }
435
- end
436
- end
437
-
438
- def remove_records(existing_records, records, method)
439
- records.each { |record| callback(:before_remove, record) }
441
+ transaction do
442
+ records.each { |record| callback(:before_remove, record) }
440
443
 
441
- delete_records(existing_records, method) if existing_records.any?
442
- records.each { |record| target.delete(record) }
444
+ delete_records(existing_records, method) if existing_records.any?
445
+ records.each { |record| target.delete(record) }
443
446
 
444
- records.each { |record| callback(:after_remove, record) }
447
+ records.each { |record| callback(:after_remove, record) }
448
+ end
445
449
  end
446
450
 
447
451
  # Delete the given records from the association, using one of the methods :destroy,
@@ -450,29 +454,6 @@ module ActiveRecord
450
454
  raise NotImplementedError
451
455
  end
452
456
 
453
- def replace_records(new_target, original_target)
454
- delete(target - new_target)
455
-
456
- unless concat(new_target - target)
457
- @target = original_target
458
- raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
459
- "new records could not be saved."
460
- end
461
- end
462
-
463
- def concat_records(records)
464
- result = true
465
-
466
- records.flatten.each do |record|
467
- raise_on_type_mismatch(record)
468
- add_to_target(record) do |r|
469
- result &&= insert_record(record) unless owner.new_record?
470
- end
471
- end
472
-
473
- result && records
474
- end
475
-
476
457
  def callback(method, record)
477
458
  callbacks_for(method).each do |callback|
478
459
  case callback
@@ -65,23 +65,27 @@ module ActiveRecord
65
65
 
66
66
  alias_method :new, :build
67
67
 
68
+ def proxy_association
69
+ @association
70
+ end
71
+
68
72
  def respond_to?(name, include_private = false)
69
73
  super ||
70
74
  (load_target && target.respond_to?(name, include_private)) ||
71
- @association.klass.respond_to?(name, include_private)
75
+ proxy_association.klass.respond_to?(name, include_private)
72
76
  end
73
77
 
74
78
  def method_missing(method, *args, &block)
75
79
  match = DynamicFinderMatch.match(method)
76
80
  if match && match.instantiator?
77
81
  record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
78
- @association.send :set_owner_attributes, r
79
- @association.send :add_to_target, r
82
+ proxy_association.send :set_owner_attributes, r
83
+ proxy_association.send :add_to_target, r
80
84
  yield(r) if block_given?
81
85
  end
82
86
  end
83
87
 
84
- if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method))
88
+ if target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
85
89
  if load_target
86
90
  if target.respond_to?(method)
87
91
  target.send(method, *args, &block)
@@ -111,7 +115,7 @@ module ActiveRecord
111
115
  alias_method :to_a, :to_ary
112
116
 
113
117
  def <<(*records)
114
- @association.concat(records) && self
118
+ proxy_association.concat(records) && self
115
119
  end
116
120
  alias_method :push, :<<
117
121
 
@@ -121,32 +125,35 @@ module ActiveRecord
121
125
  end
122
126
 
123
127
  def reload
124
- @association.reload
128
+ proxy_association.reload
125
129
  self
126
130
  end
127
131
 
128
132
  def proxy_owner
129
133
  ActiveSupport::Deprecation.warn(
130
134
  "Calling record.#{@association.reflection.name}.proxy_owner is deprecated. Please use " \
131
- "record.association(:#{@association.reflection.name}).owner instead."
135
+ "record.association(:#{@association.reflection.name}).owner instead. Or, from an " \
136
+ "association extension you can access proxy_association.owner."
132
137
  )
133
- @association.owner
138
+ proxy_association.owner
134
139
  end
135
140
 
136
141
  def proxy_target
137
142
  ActiveSupport::Deprecation.warn(
138
143
  "Calling record.#{@association.reflection.name}.proxy_target is deprecated. Please use " \
139
- "record.association(:#{@association.reflection.name}).target instead."
144
+ "record.association(:#{@association.reflection.name}).target instead. Or, from an " \
145
+ "association extension you can access proxy_association.target."
140
146
  )
141
- @association.target
147
+ proxy_association.target
142
148
  end
143
149
 
144
150
  def proxy_reflection
145
151
  ActiveSupport::Deprecation.warn(
146
152
  "Calling record.#{@association.reflection.name}.proxy_reflection is deprecated. Please use " \
147
- "record.association(:#{@association.reflection.name}).reflection instead."
153
+ "record.association(:#{@association.reflection.name}).reflection instead. Or, from an " \
154
+ "association extension you can access proxy_association.reflection."
148
155
  )
149
- @association.reflection
156
+ proxy_association.reflection
150
157
  end
151
158
  end
152
159
  end
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  join_table[reflection.association_foreign_key] => record.id
27
27
  )
28
28
 
29
- owner.connection.insert stmt.to_sql
29
+ owner.connection.insert stmt
30
30
  end
31
31
 
32
32
  record
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  stmt = relation.where(relation[reflection.foreign_key].eq(owner.id).
47
47
  and(relation[reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
48
48
  ).compile_delete
49
- owner.connection.delete stmt.to_sql
49
+ owner.connection.delete stmt
50
50
  end
51
51
  end
52
52
 
@@ -188,13 +188,12 @@ module ActiveRecord
188
188
  association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
189
189
  set_target_and_inverse(join_part, association, record)
190
190
  else
191
- return if row[join_part.aliased_primary_key].nil?
192
- association = join_part.instantiate(row)
191
+ association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
193
192
  case macro
194
193
  when :has_many, :has_and_belongs_to_many
195
194
  other = record.association(join_part.reflection.name)
196
195
  other.loaded!
197
- other.target.push(association)
196
+ other.target.push(association) if association
198
197
  other.set_inverse_instance(association)
199
198
  when :belongs_to
200
199
  set_target_and_inverse(join_part, association, record)
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  # access the aliased column on the join table
14
14
  def records_for(ids)
15
15
  scope = super
16
- klass.connection.select_all(scope.arel.to_sql, 'SQL', scope.bind_values)
16
+ klass.connection.select_all(scope.arel, 'SQL', scope.bind_values)
17
17
  end
18
18
 
19
19
  def owner_key_name
@@ -3,11 +3,10 @@ module ActiveRecord
3
3
  module PrimaryKey
4
4
  extend ActiveSupport::Concern
5
5
 
6
- # Returns this record's primary key value wrapped in an Array or nil if
7
- # the record is not persisted? or has just been destroyed.
6
+ # Returns this record's primary key value wrapped in an Array if one is available
8
7
  def to_key
9
8
  key = send(self.class.primary_key)
10
- persisted? && key ? [key] : nil
9
+ [key] if key
11
10
  end
12
11
 
13
12
  module ClassMethods
@@ -940,17 +940,6 @@ module ActiveRecord #:nodoc:
940
940
  self.current_scope = nil
941
941
  end
942
942
 
943
- # Specifies how the record is loaded by +Marshal+.
944
- #
945
- # +_load+ sets an instance variable for each key in the hash it takes as input.
946
- # Override this method if you require more complex marshalling.
947
- def _load(data)
948
- record = allocate
949
- record.init_with(Marshal.load(data))
950
- record
951
- end
952
-
953
-
954
943
  # Finder methods must instantiate through this method to work with the
955
944
  # single-table inheritance model that makes it possible to create
956
945
  # objects of different types from the same table.
@@ -1057,12 +1046,10 @@ module ActiveRecord #:nodoc:
1057
1046
  if match = DynamicFinderMatch.match(method_id)
1058
1047
  attribute_names = match.attribute_names
1059
1048
  super unless all_attributes_exists?(attribute_names)
1060
- if arguments.size < attribute_names.size
1061
- ActiveSupport::Deprecation.warn(
1062
- "Calling dynamic finder with less number of arguments than the number of attributes in " \
1063
- "method name is deprecated and will raise an ArguementError in the next version of Rails. " \
1064
- "Please passing `nil' to the argument you want it to be nil."
1065
- )
1049
+ if !arguments.first.is_a?(Hash) && arguments.size < attribute_names.size
1050
+ ActiveSupport::Deprecation.warn(<<-eowarn)
1051
+ Calling dynamic finder with less number of arguments than the number of attributes in method name is deprecated and will raise an ArguementError in the next version of Rails. Please passing `nil' to the argument you want it to be nil.
1052
+ eowarn
1066
1053
  end
1067
1054
  if match.finder?
1068
1055
  options = arguments.extract_options!
@@ -1283,27 +1270,43 @@ MSG
1283
1270
  self.default_scopes = default_scopes + [scope]
1284
1271
  end
1285
1272
 
1286
- # The @ignore_default_scope flag is used to prevent an infinite recursion situation where
1287
- # a default scope references a scope which has a default scope which references a scope...
1288
1273
  def build_default_scope #:nodoc:
1289
- return if defined?(@ignore_default_scope) && @ignore_default_scope
1290
- @ignore_default_scope = true
1291
-
1292
1274
  if method(:default_scope).owner != Base.singleton_class
1293
- default_scope
1275
+ evaluate_default_scope { default_scope }
1294
1276
  elsif default_scopes.any?
1295
- default_scopes.inject(relation) do |default_scope, scope|
1296
- if scope.is_a?(Hash)
1297
- default_scope.apply_finder_options(scope)
1298
- elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
1299
- default_scope.merge(scope.call)
1300
- else
1301
- default_scope.merge(scope)
1277
+ evaluate_default_scope do
1278
+ default_scopes.inject(relation) do |default_scope, scope|
1279
+ if scope.is_a?(Hash)
1280
+ default_scope.apply_finder_options(scope)
1281
+ elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
1282
+ default_scope.merge(scope.call)
1283
+ else
1284
+ default_scope.merge(scope)
1285
+ end
1302
1286
  end
1303
1287
  end
1304
1288
  end
1305
- ensure
1306
- @ignore_default_scope = false
1289
+ end
1290
+
1291
+ def ignore_default_scope? #:nodoc:
1292
+ Thread.current["#{self}_ignore_default_scope"]
1293
+ end
1294
+
1295
+ def ignore_default_scope=(ignore) #:nodoc:
1296
+ Thread.current["#{self}_ignore_default_scope"] = ignore
1297
+ end
1298
+
1299
+ # The ignore_default_scope flag is used to prevent an infinite recursion situation where
1300
+ # a default scope references a scope which has a default scope which references a scope...
1301
+ def evaluate_default_scope
1302
+ return if ignore_default_scope?
1303
+
1304
+ begin
1305
+ self.ignore_default_scope = true
1306
+ yield
1307
+ ensure
1308
+ self.ignore_default_scope = false
1309
+ end
1307
1310
  end
1308
1311
 
1309
1312
  # Returns the class type of the record using the current module as a prefix. So descendants of
@@ -1425,9 +1428,8 @@ MSG
1425
1428
  attrs = expand_hash_conditions_for_aggregates(attrs)
1426
1429
 
1427
1430
  table = Arel::Table.new(table_name).alias(default_table_name)
1428
- viz = Arel::Visitors.for(arel_engine)
1429
1431
  PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
1430
- viz.accept b
1432
+ connection.visitor.accept b
1431
1433
  }.join(' AND ')
1432
1434
  end
1433
1435
  alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
@@ -1605,16 +1607,6 @@ MSG
1605
1607
  self
1606
1608
  end
1607
1609
 
1608
- # Specifies how the record is dumped by +Marshal+.
1609
- #
1610
- # +_dump+ emits a marshalled hash which has been passed to +encode_with+. Override this
1611
- # method if you require more complex marshalling.
1612
- def _dump(level)
1613
- dump = {}
1614
- encode_with(dump)
1615
- Marshal.dump(dump)
1616
- end
1617
-
1618
1610
  # Returns a String, which Action Pack uses for constructing an URL to this
1619
1611
  # object. The default implementation returns this record's id as a String,
1620
1612
  # or nil if this record's unsaved.
@@ -82,10 +82,11 @@ module ActiveRecord
82
82
  # default max pool size to 5
83
83
  @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
84
84
 
85
- @connections = []
86
- @checked_out = []
85
+ @connections = []
86
+ @checked_out = []
87
87
  @automatic_reconnect = true
88
- @tables = {}
88
+ @tables = {}
89
+ @visitor = nil
89
90
 
90
91
  @columns = Hash.new do |h, table_name|
91
92
  h[table_name] = with_connection do |conn|
@@ -298,8 +299,18 @@ module ActiveRecord
298
299
  :connected?, :disconnect!, :with => :@connection_mutex
299
300
 
300
301
  private
302
+
301
303
  def new_connection
302
- ActiveRecord::Base.send(spec.adapter_method, spec.config)
304
+ connection = ActiveRecord::Base.send(spec.adapter_method, spec.config)
305
+
306
+ # TODO: This is a bit icky, and in the long term we may want to change the method
307
+ # signature for connections. Also, if we switch to have one visitor per
308
+ # connection (and therefore per thread), we can get rid of the thread-local
309
+ # variable in Arel::Visitors::ToSql.
310
+ @visitor ||= connection.class.visitor_for(self)
311
+ connection.visitor = @visitor
312
+
313
+ connection
303
314
  end
304
315
 
305
316
  def current_connection_id #:nodoc:
@@ -3,30 +3,39 @@ require 'active_support/core_ext/module/deprecation'
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters # :nodoc:
5
5
  module DatabaseStatements
6
+ # Converts an arel AST to SQL
7
+ def to_sql(arel)
8
+ if arel.respond_to?(:ast)
9
+ visitor.accept(arel.ast)
10
+ else
11
+ arel
12
+ end
13
+ end
14
+
6
15
  # Returns an array of record hashes with the column names as keys and
7
16
  # column values as values.
8
- def select_all(sql, name = nil, binds = [])
9
- select(sql, name, binds)
17
+ def select_all(arel, name = nil, binds = [])
18
+ select(to_sql(arel), name, binds)
10
19
  end
11
20
 
12
21
  # Returns a record hash with the column names as keys and column values
13
22
  # as values.
14
- def select_one(sql, name = nil)
15
- result = select_all(sql, name)
23
+ def select_one(arel, name = nil)
24
+ result = select_all(arel, name)
16
25
  result.first if result
17
26
  end
18
27
 
19
28
  # Returns a single value from a record
20
- def select_value(sql, name = nil)
21
- if result = select_one(sql, name)
29
+ def select_value(arel, name = nil)
30
+ if result = select_one(arel, name)
22
31
  result.values.first
23
32
  end
24
33
  end
25
34
 
26
35
  # Returns an array of the values of the first column in a select:
27
36
  # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
28
- def select_values(sql, name = nil)
29
- result = select_rows(sql, name)
37
+ def select_values(arel, name = nil)
38
+ result = select_rows(to_sql(arel), name)
30
39
  result.map { |v| v[0] }
31
40
  end
32
41
 
@@ -76,20 +85,20 @@ module ActiveRecord
76
85
  #
77
86
  # If the next id was calculated in advance (as in Oracle), it should be
78
87
  # passed in as +id_value+.
79
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
80
- sql, binds = sql_for_insert(sql, pk, id_value, sequence_name, binds)
88
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
89
+ sql, binds = sql_for_insert(to_sql(arel), pk, id_value, sequence_name, binds)
81
90
  value = exec_insert(sql, name, binds)
82
91
  id_value || last_inserted_id(value)
83
92
  end
84
93
 
85
94
  # Executes the update statement and returns the number of rows affected.
86
- def update(sql, name = nil, binds = [])
87
- exec_update(sql, name, binds)
95
+ def update(arel, name = nil, binds = [])
96
+ exec_update(to_sql(arel), name, binds)
88
97
  end
89
98
 
90
99
  # Executes the delete statement and returns the number of rows affected.
91
- def delete(sql, name = nil, binds = [])
92
- exec_delete(sql, name, binds)
100
+ def delete(arel, name = nil, binds = [])
101
+ exec_delete(to_sql(arel), name, binds)
93
102
  end
94
103
 
95
104
  # Checks whether there is currently no transaction active. This is done
@@ -324,6 +333,16 @@ module ActiveRecord
324
333
  end
325
334
  end
326
335
 
336
+ # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
337
+ # on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
338
+ # an UPDATE statement, so in the mysql adapters we redefine this to do that.
339
+ def join_to_update(update, select) #:nodoc:
340
+ subselect = select.clone
341
+ subselect.projections = [update.key]
342
+
343
+ update.where update.key.in(subselect)
344
+ end
345
+
327
346
  protected
328
347
  # Returns an array of record hashes with the column names as keys and
329
348
  # column values as values.
@@ -55,9 +55,10 @@ module ActiveRecord
55
55
  @query_cache.clear
56
56
  end
57
57
 
58
- def select_all(sql, name = nil, binds = [])
58
+ def select_all(arel, name = nil, binds = [])
59
59
  if @query_cache_enabled
60
- cache_sql(sql, binds) { super }
60
+ sql = to_sql(arel)
61
+ cache_sql(sql, binds) { super(sql, name, binds) }
61
62
  else
62
63
  super
63
64
  end
@@ -2,6 +2,7 @@ require 'date'
2
2
  require 'bigdecimal'
3
3
  require 'bigdecimal/util'
4
4
  require 'active_support/core_ext/benchmark'
5
+ require 'active_support/deprecation'
5
6
 
6
7
  # TODO: Autoload these files
7
8
  require 'active_record/connection_adapters/column'
@@ -38,12 +39,33 @@ module ActiveRecord
38
39
 
39
40
  define_callbacks :checkout, :checkin
40
41
 
42
+ attr_accessor :visitor
43
+
41
44
  def initialize(connection, logger = nil) #:nodoc:
42
45
  @active = nil
43
46
  @connection, @logger = connection, logger
44
47
  @query_cache_enabled = false
45
48
  @query_cache = Hash.new { |h,sql| h[sql] = {} }
46
49
  @instrumenter = ActiveSupport::Notifications.instrumenter
50
+ @visitor = nil
51
+ end
52
+
53
+ # Returns a visitor instance for this adaptor, which conforms to the Arel::ToSql interface
54
+ def self.visitor_for(pool) # :nodoc:
55
+ adapter = pool.spec.config[:adapter]
56
+
57
+ if Arel::Visitors::VISITORS[adapter]
58
+ ActiveSupport::Deprecation.warn(
59
+ "Arel::Visitors::VISITORS is deprecated and will be removed. Database adapters " \
60
+ "should define a visitor_for method which returns the appropriate visitor for " \
61
+ "the database. For example, MysqlAdapter.visitor_for(pool) returns " \
62
+ "Arel::Visitors::MySQL.new(pool)."
63
+ )
64
+
65
+ Arel::Visitors::VISITORS[adapter].new(pool)
66
+ else
67
+ Arel::Visitors::ToSql.new(pool)
68
+ end
47
69
  end
48
70
 
49
71
  # Returns the human-readable name of the adapter. Use mixed case - one
@@ -129,6 +129,10 @@ module ActiveRecord
129
129
  configure_connection
130
130
  end
131
131
 
132
+ def self.visitor_for(pool) # :nodoc:
133
+ Arel::Visitors::MySQL.new(pool)
134
+ end
135
+
132
136
  def adapter_name
133
137
  ADAPTER_NAME
134
138
  end
@@ -165,7 +169,7 @@ module ActiveRecord
165
169
  end
166
170
 
167
171
  def quote_column_name(name) #:nodoc:
168
- @quoted_column_names[name] ||= "`#{name}`"
172
+ @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
169
173
  end
170
174
 
171
175
  def quote_table_name(name) #:nodoc:
@@ -595,6 +599,27 @@ module ActiveRecord
595
599
  where_sql
596
600
  end
597
601
 
602
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
603
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
604
+ # these, we must use a subquery. However, MySQL is too stupid to create a
605
+ # temporary table for this automatically, so we have to give it some prompting
606
+ # in the form of a subsubquery. Ugh!
607
+ def join_to_update(update, select) #:nodoc:
608
+ if select.limit || select.offset || select.orders.any?
609
+ subsubselect = select.clone
610
+ subsubselect.projections = [update.key]
611
+
612
+ subselect = Arel::SelectManager.new(select.engine)
613
+ subselect.project Arel.sql(update.key.name)
614
+ subselect.from subsubselect.as('__active_record_temp')
615
+
616
+ update.where update.key.in(subselect)
617
+ else
618
+ update.table select.source
619
+ update.wheres = select.constraints
620
+ end
621
+ end
622
+
598
623
  protected
599
624
  def quoted_columns_for_index(column_names, options = {})
600
625
  length = options[:length] if options.is_a?(Hash)
@@ -193,6 +193,10 @@ module ActiveRecord
193
193
  connect
194
194
  end
195
195
 
196
+ def self.visitor_for(pool) # :nodoc:
197
+ Arel::Visitors::MySQL.new(pool)
198
+ end
199
+
196
200
  def adapter_name #:nodoc:
197
201
  ADAPTER_NAME
198
202
  end
@@ -247,7 +251,7 @@ module ActiveRecord
247
251
  end
248
252
 
249
253
  def quote_column_name(name) #:nodoc:
250
- @quoted_column_names[name] ||= "`#{name}`"
254
+ @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
251
255
  end
252
256
 
253
257
  def quote_table_name(name) #:nodoc:
@@ -501,6 +505,27 @@ module ActiveRecord
501
505
  end
502
506
  deprecate :add_limit_offset!
503
507
 
508
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
509
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
510
+ # these, we must use a subquery. However, MySQL is too stupid to create a
511
+ # temporary table for this automatically, so we have to give it some prompting
512
+ # in the form of a subsubquery. Ugh!
513
+ def join_to_update(update, select) #:nodoc:
514
+ if select.limit || select.offset || select.orders.any?
515
+ subsubselect = select.clone
516
+ subsubselect.projections = [update.key]
517
+
518
+ subselect = Arel::SelectManager.new(select.engine)
519
+ subselect.project Arel.sql(update.key.name)
520
+ subselect.from subsubselect.as('__active_record_temp')
521
+
522
+ update.where update.key.in(subselect)
523
+ else
524
+ update.table select.source
525
+ update.wheres = select.constraints
526
+ end
527
+ end
528
+
504
529
  # SCHEMA STATEMENTS ========================================
505
530
 
506
531
  def structure_dump #:nodoc:
@@ -266,6 +266,10 @@ module ActiveRecord
266
266
  @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
267
267
  end
268
268
 
269
+ def self.visitor_for(pool) # :nodoc:
270
+ Arel::Visitors::PostgreSQL.new(pool)
271
+ end
272
+
269
273
  # Clears the prepared statements cache.
270
274
  def clear_cache!
271
275
  @statements.each_value do |value|
@@ -1,4 +1,6 @@
1
1
  require 'active_record/connection_adapters/sqlite_adapter'
2
+
3
+ gem 'sqlite3', '~> 1.3.4'
2
4
  require 'sqlite3'
3
5
 
4
6
  module ActiveRecord
@@ -54,6 +54,10 @@ module ActiveRecord
54
54
  @config = config
55
55
  end
56
56
 
57
+ def self.visitor_for(pool) # :nodoc:
58
+ Arel::Visitors::SQLite.new(pool)
59
+ end
60
+
57
61
  def adapter_name #:nodoc:
58
62
  'SQLite'
59
63
  end
@@ -142,7 +146,7 @@ module ActiveRecord
142
146
  end
143
147
 
144
148
  def quote_column_name(name) #:nodoc:
145
- %Q("#{name}")
149
+ %Q("#{name.to_s.gsub('"', '""')}")
146
150
  end
147
151
 
148
152
  # Quote date/time values for use in SQL input. Includes microseconds
@@ -33,7 +33,7 @@ module ActiveRecord
33
33
  stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
34
34
  arel_table[counter_name] => object.send(association).count
35
35
  })
36
- connection.update stmt.to_sql
36
+ connection.update stmt
37
37
  end
38
38
  return true
39
39
  end
@@ -70,7 +70,7 @@ module ActiveRecord
70
70
 
71
71
  # If the locking column has no default value set,
72
72
  # start the lock version at zero. Note we can't use
73
- # <tt>locking_enabled?</tt> at this point as
73
+ # <tt>locking_enabled?</tt> at this point as
74
74
  # <tt>@attributes</tt> may not have been initialized yet.
75
75
 
76
76
  if result.key?(self.class.locking_column) && lock_optimistically
@@ -100,7 +100,7 @@ module ActiveRecord
100
100
  )
101
101
  ).arel.compile_update(arel_attributes_values(false, false, attribute_names))
102
102
 
103
- affected_rows = connection.update stmt.to_sql
103
+ affected_rows = connection.update stmt
104
104
 
105
105
  unless affected_rows == 1
106
106
  raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
@@ -332,6 +332,10 @@ module ActiveRecord
332
332
  (delegate || superclass.delegate).send(name, *args, &block)
333
333
  end
334
334
 
335
+ def self.migrate(direction)
336
+ new.migrate direction
337
+ end
338
+
335
339
  cattr_accessor :verbose
336
340
 
337
341
  attr_accessor :name, :version
@@ -559,7 +563,7 @@ module ActiveRecord
559
563
 
560
564
  def get_all_versions
561
565
  table = Arel::Table.new(schema_migrations_table_name)
562
- Base.connection.select_values(table.project(table['version']).to_sql).map{ |v| v.to_i }.sort
566
+ Base.connection.select_values(table.project(table['version'])).map{ |v| v.to_i }.sort
563
567
  end
564
568
 
565
569
  def current_version
@@ -716,11 +720,11 @@ module ActiveRecord
716
720
  if down?
717
721
  @migrated_versions.delete(version)
718
722
  stmt = table.where(table["version"].eq(version.to_s)).compile_delete
719
- Base.connection.delete stmt.to_sql
723
+ Base.connection.delete stmt
720
724
  else
721
725
  @migrated_versions.push(version).sort!
722
726
  stmt = table.compile_insert table["version"] => version.to_s
723
- Base.connection.insert stmt.to_sql
727
+ Base.connection.insert stmt
724
728
  end
725
729
  end
726
730
 
@@ -111,7 +111,7 @@ module ActiveRecord
111
111
  callback_meth = :"_notify_#{observer_name}_for_#{callback}"
112
112
  unless klass.respond_to?(callback_meth)
113
113
  klass.send(:define_method, callback_meth) do |&block|
114
- observer.send(callback, self, &block)
114
+ observer.update(callback, self, &block)
115
115
  end
116
116
  klass.send(callback, callback_meth)
117
117
  end
@@ -302,7 +302,7 @@ module ActiveRecord
302
302
  return 0 if attributes_with_values.empty?
303
303
  klass = self.class
304
304
  stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
305
- klass.connection.update stmt.to_sql
305
+ klass.connection.update stmt
306
306
  end
307
307
 
308
308
  # Creates a record with values matching those of the instance attributes
@@ -341,7 +341,8 @@ db_namespace = namespace :db do
341
341
  desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR'
342
342
  task :dump => :load_config do
343
343
  require 'active_record/schema_dumper'
344
- File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file|
344
+ filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
345
+ File.open(filename, "w:utf-8") do |file|
345
346
  ActiveRecord::Base.establish_connection(Rails.env)
346
347
  ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
347
348
  end
@@ -68,7 +68,7 @@ module ActiveRecord
68
68
  end
69
69
 
70
70
  conn.insert(
71
- im.to_sql,
71
+ im,
72
72
  'SQL',
73
73
  primary_key,
74
74
  primary_key_value,
@@ -108,10 +108,10 @@ module ActiveRecord
108
108
 
109
109
  if default_scoped.equal?(self)
110
110
  @records = if @readonly_value.nil? && !@klass.locking_enabled?
111
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values)
111
+ eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
112
112
  else
113
113
  IdentityMap.without do
114
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values)
114
+ eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
115
115
  end
116
116
  end
117
117
 
@@ -216,15 +216,21 @@ module ActiveRecord
216
216
  if conditions || options.present?
217
217
  where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
218
218
  else
219
- stmt = arel.compile_update(Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)))
219
+ stmt = Arel::UpdateManager.new(arel.engine)
220
220
 
221
- if limit = arel.limit
222
- stmt.take limit
221
+ stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
222
+ stmt.table(table)
223
+ stmt.key = table[primary_key]
224
+
225
+ if joins_values.any?
226
+ @klass.connection.join_to_update(stmt, arel)
227
+ else
228
+ stmt.take(arel.limit)
229
+ stmt.order(*arel.orders)
230
+ stmt.wheres = arel.constraints
223
231
  end
224
232
 
225
- stmt.order(*arel.orders)
226
- stmt.key = table[primary_key]
227
- @klass.connection.update stmt.to_sql, 'SQL', bind_values
233
+ @klass.connection.update stmt, 'SQL', bind_values
228
234
  end
229
235
  end
230
236
 
@@ -342,8 +348,7 @@ module ActiveRecord
342
348
  where(conditions).delete_all
343
349
  else
344
350
  statement = arel.compile_delete
345
- affected = @klass.connection.delete(
346
- statement.to_sql, 'SQL', bind_values)
351
+ affected = @klass.connection.delete(statement, 'SQL', bind_values)
347
352
 
348
353
  reset
349
354
  affected
@@ -389,7 +394,7 @@ module ActiveRecord
389
394
  end
390
395
 
391
396
  def to_sql
392
- @to_sql ||= arel.to_sql
397
+ @to_sql ||= klass.connection.to_sql(arel)
393
398
  end
394
399
 
395
400
  def where_values_hash
@@ -219,7 +219,7 @@ module ActiveRecord
219
219
  query_builder = relation.arel
220
220
  end
221
221
 
222
- type_cast_calculated_value(@klass.connection.select_value(query_builder.to_sql), column_for(column_name), operation)
222
+ type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation)
223
223
  end
224
224
 
225
225
  def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
@@ -254,7 +254,7 @@ module ActiveRecord
254
254
  relation = except(:group).group(group.join(','))
255
255
  relation.select_values = select_values
256
256
 
257
- calculated_data = @klass.connection.select_all(relation.to_sql)
257
+ calculated_data = @klass.connection.select_all(relation)
258
258
 
259
259
  if association
260
260
  key_ids = calculated_data.collect { |row| row[group_aliases.first] }
@@ -194,7 +194,7 @@ module ActiveRecord
194
194
  relation = relation.where(table[primary_key].eq(id)) if id
195
195
  end
196
196
 
197
- connection.select_value(relation.to_sql) ? true : false
197
+ connection.select_value(relation) ? true : false
198
198
  end
199
199
 
200
200
  protected
@@ -202,7 +202,7 @@ module ActiveRecord
202
202
  def find_with_associations
203
203
  join_dependency = construct_join_dependency_for_association_find
204
204
  relation = construct_relation_for_association_find(join_dependency)
205
- rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values)
205
+ rows = connection.select_all(relation, 'SQL', relation.bind_values)
206
206
  join_dependency.instantiate(rows)
207
207
  rescue ThrowResult
208
208
  []
@@ -40,6 +40,10 @@ module ActiveRecord
40
40
  def header(stream)
41
41
  define_params = @version ? ":version => #{@version}" : ""
42
42
 
43
+ if stream.respond_to?(:external_encoding)
44
+ stream.puts "# encoding: #{stream.external_encoding.name}"
45
+ end
46
+
43
47
  stream.puts <<HEADER
44
48
  # This file is auto-generated from the current state of the database. Instead
45
49
  # of editing this file, please use the migrations feature of Active Record to
@@ -17,15 +17,7 @@ module ActiveRecord
17
17
  # validates_associated :pages, :library
18
18
  # end
19
19
  #
20
- # Warning: If, after the above definition, you then wrote:
21
- #
22
- # class Page < ActiveRecord::Base
23
- # belongs_to :book
24
- #
25
- # validates_associated :book
26
- # end
27
- #
28
- # this would specify a circular dependency and cause infinite recursion.
20
+ # WARNING: This validation must not be used on both ends of an association. Doing so will lead to a circular dependency and cause infinite recursion.
29
21
  #
30
22
  # NOTE: This validation will not fail if the association hasn't been assigned. If you want to
31
23
  # ensure that the association is both present and guaranteed to be valid, you also need to
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  MAJOR = 3
4
4
  MINOR = 1
5
5
  TINY = 0
6
- PRE = "rc5"
6
+ PRE = "rc6"
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
9
9
  end
metadata CHANGED
@@ -1,13 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: true
4
+ hash: 15424105
5
+ prerelease: 6
5
6
  segments:
6
7
  - 3
7
8
  - 1
8
9
  - 0
9
- - rc5
10
- version: 3.1.0.rc5
10
+ - rc
11
+ - 6
12
+ version: 3.1.0.rc6
11
13
  platform: ruby
12
14
  authors:
13
15
  - David Heinemeier Hansson
@@ -15,60 +17,69 @@ autorequire:
15
17
  bindir: bin
16
18
  cert_chain: []
17
19
 
18
- date: 2011-07-25 00:00:00 -07:00
19
- default_executable:
20
+ date: 2011-08-16 00:00:00 Z
20
21
  dependencies:
21
22
  - !ruby/object:Gem::Dependency
22
23
  name: activesupport
23
24
  prerelease: false
24
25
  requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
25
27
  requirements:
26
28
  - - "="
27
29
  - !ruby/object:Gem::Version
30
+ hash: 15424105
28
31
  segments:
29
32
  - 3
30
33
  - 1
31
34
  - 0
32
- - rc5
33
- version: 3.1.0.rc5
35
+ - rc
36
+ - 6
37
+ version: 3.1.0.rc6
34
38
  type: :runtime
35
39
  version_requirements: *id001
36
40
  - !ruby/object:Gem::Dependency
37
41
  name: activemodel
38
42
  prerelease: false
39
43
  requirement: &id002 !ruby/object:Gem::Requirement
44
+ none: false
40
45
  requirements:
41
46
  - - "="
42
47
  - !ruby/object:Gem::Version
48
+ hash: 15424105
43
49
  segments:
44
50
  - 3
45
51
  - 1
46
52
  - 0
47
- - rc5
48
- version: 3.1.0.rc5
53
+ - rc
54
+ - 6
55
+ version: 3.1.0.rc6
49
56
  type: :runtime
50
57
  version_requirements: *id002
51
58
  - !ruby/object:Gem::Dependency
52
59
  name: arel
53
60
  prerelease: false
54
61
  requirement: &id003 !ruby/object:Gem::Requirement
62
+ none: false
55
63
  requirements:
56
64
  - - ~>
57
65
  - !ruby/object:Gem::Version
66
+ hash: 5
58
67
  segments:
59
68
  - 2
69
+ - 2
60
70
  - 1
61
- - 4
62
- version: 2.1.4
71
+ version: 2.2.1
63
72
  type: :runtime
64
73
  version_requirements: *id003
65
74
  - !ruby/object:Gem::Dependency
66
75
  name: tzinfo
67
76
  prerelease: false
68
77
  requirement: &id004 !ruby/object:Gem::Requirement
78
+ none: false
69
79
  requirements:
70
80
  - - ~>
71
81
  - !ruby/object:Gem::Version
82
+ hash: 41
72
83
  segments:
73
84
  - 0
74
85
  - 3
@@ -212,7 +223,6 @@ files:
212
223
  - lib/rails/generators/active_record/session_migration/session_migration_generator.rb
213
224
  - lib/rails/generators/active_record/session_migration/templates/migration.rb
214
225
  - lib/rails/generators/active_record.rb
215
- has_rdoc: true
216
226
  homepage: http://www.rubyonrails.org
217
227
  licenses: []
218
228
 
@@ -223,18 +233,22 @@ rdoc_options:
223
233
  require_paths:
224
234
  - lib
225
235
  required_ruby_version: !ruby/object:Gem::Requirement
236
+ none: false
226
237
  requirements:
227
238
  - - ">="
228
239
  - !ruby/object:Gem::Version
240
+ hash: 57
229
241
  segments:
230
242
  - 1
231
243
  - 8
232
244
  - 7
233
245
  version: 1.8.7
234
246
  required_rubygems_version: !ruby/object:Gem::Requirement
247
+ none: false
235
248
  requirements:
236
249
  - - ">"
237
250
  - !ruby/object:Gem::Version
251
+ hash: 25
238
252
  segments:
239
253
  - 1
240
254
  - 3
@@ -243,7 +257,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
243
257
  requirements: []
244
258
 
245
259
  rubyforge_project:
246
- rubygems_version: 1.3.6
260
+ rubygems_version: 1.8.8
247
261
  signing_key:
248
262
  specification_version: 3
249
263
  summary: Object-relational mapper framework (part of Rails).