activerecord 4.0.0 → 4.0.1.rc1

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +326 -3
  3. data/README.rdoc +1 -1
  4. data/lib/active_record.rb +1 -0
  5. data/lib/active_record/association_relation.rb +18 -0
  6. data/lib/active_record/associations/association.rb +11 -3
  7. data/lib/active_record/associations/builder/belongs_to.rb +4 -2
  8. data/lib/active_record/associations/collection_association.rb +8 -8
  9. data/lib/active_record/associations/collection_proxy.rb +2 -4
  10. data/lib/active_record/associations/has_many_association.rb +2 -2
  11. data/lib/active_record/associations/has_one_association.rb +3 -1
  12. data/lib/active_record/associations/join_dependency/join_association.rb +2 -0
  13. data/lib/active_record/associations/join_dependency/join_part.rb +14 -1
  14. data/lib/active_record/associations/preloader.rb +3 -2
  15. data/lib/active_record/attribute_methods.rb +26 -2
  16. data/lib/active_record/attribute_methods/read.rb +15 -9
  17. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  18. data/lib/active_record/attribute_methods/write.rb +2 -0
  19. data/lib/active_record/autosave_association.rb +8 -8
  20. data/lib/active_record/callbacks.rb +4 -1
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -7
  22. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +12 -3
  23. data/lib/active_record/connection_adapters/abstract_adapter.rb +16 -2
  24. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +3 -1
  25. data/lib/active_record/connection_adapters/column.rb +12 -11
  26. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +19 -9
  28. data/lib/active_record/connection_adapters/postgresql/cast.rb +1 -1
  29. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +7 -6
  30. data/lib/active_record/connection_adapters/postgresql/oid.rb +5 -0
  31. data/lib/active_record/connection_adapters/postgresql/quoting.rb +2 -0
  32. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +7 -10
  33. data/lib/active_record/connection_adapters/postgresql_adapter.rb +19 -13
  34. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +8 -5
  35. data/lib/active_record/core.rb +18 -18
  36. data/lib/active_record/dynamic_matchers.rb +1 -1
  37. data/lib/active_record/fixtures.rb +2 -2
  38. data/lib/active_record/locking/optimistic.rb +1 -1
  39. data/lib/active_record/migration.rb +1 -1
  40. data/lib/active_record/migration/command_recorder.rb +4 -1
  41. data/lib/active_record/model_schema.rb +27 -17
  42. data/lib/active_record/null_relation.rb +1 -5
  43. data/lib/active_record/persistence.rb +16 -5
  44. data/lib/active_record/railtie.rb +1 -0
  45. data/lib/active_record/railties/databases.rake +4 -1
  46. data/lib/active_record/reflection.rb +1 -1
  47. data/lib/active_record/relation.rb +1 -1
  48. data/lib/active_record/relation/batches.rb +2 -2
  49. data/lib/active_record/relation/calculations.rb +1 -0
  50. data/lib/active_record/relation/finder_methods.rb +10 -10
  51. data/lib/active_record/relation/merger.rb +31 -28
  52. data/lib/active_record/relation/query_methods.rb +13 -11
  53. data/lib/active_record/result.rb +15 -1
  54. data/lib/active_record/sanitization.rb +13 -3
  55. data/lib/active_record/schema_dumper.rb +5 -1
  56. data/lib/active_record/tasks/database_tasks.rb +2 -1
  57. data/lib/active_record/tasks/sqlite_database_tasks.rb +1 -1
  58. data/lib/active_record/transactions.rb +5 -5
  59. data/lib/active_record/validations/uniqueness.rb +2 -2
  60. data/lib/active_record/version.rb +1 -1
  61. metadata +10 -9
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  end
36
36
 
37
37
  def pattern
38
- /^#{prefix}_([_a-zA-Z]\w*)#{suffix}$/
38
+ @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
39
39
  end
40
40
 
41
41
  def prefix
@@ -639,7 +639,7 @@ module ActiveRecord
639
639
  end
640
640
 
641
641
  def read_fixture_files
642
- yaml_files = Dir["#{@path}/**/*.yml"].select { |f|
642
+ yaml_files = Dir["#{@path}/{**,*}/*.yml"].select { |f|
643
643
  ::File.file?(f)
644
644
  } + [yaml_file_path]
645
645
 
@@ -758,7 +758,7 @@ module ActiveRecord
758
758
 
759
759
  def fixtures(*fixture_set_names)
760
760
  if fixture_set_names.first == :all
761
- fixture_set_names = Dir["#{fixture_path}/**/*.{yml}"]
761
+ fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"]
762
762
  fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
763
763
  else
764
764
  fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
 
83
83
  stmt = relation.where(
84
84
  relation.table[self.class.primary_key].eq(id).and(
85
- relation.table[lock_col].eq(self.class.quote_value(previous_lock_value))
85
+ relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col)))
86
86
  )
87
87
  ).arel.compile_update(arel_attributes_with_values_for_update(attribute_names))
88
88
 
@@ -32,7 +32,7 @@ module ActiveRecord
32
32
 
33
33
  class PendingMigrationError < ActiveRecordError#:nodoc:
34
34
  def initialize
35
- super("Migrations are pending; run 'rake db:migrate RAILS_ENV=#{Rails.env}' to resolve this issue.")
35
+ super("Migrations are pending; run 'bin/rake db:migrate RAILS_ENV=#{Rails.env}' to resolve this issue.")
36
36
  end
37
37
  end
38
38
 
@@ -144,7 +144,10 @@ module ActiveRecord
144
144
 
145
145
  def invert_remove_index(args)
146
146
  table, options = *args
147
- raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." unless options && options[:column]
147
+
148
+ unless options && options.is_a?(Hash) && options[:column]
149
+ raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
150
+ end
148
151
 
149
152
  options = options.dup
150
153
  [:add_index, [table, options.delete(:column), options]]
@@ -224,13 +224,20 @@ module ActiveRecord
224
224
  def decorate_columns(columns_hash) # :nodoc:
225
225
  return if columns_hash.empty?
226
226
 
227
- columns_hash.each do |name, col|
228
- if serialized_attributes.key?(name)
229
- columns_hash[name] = AttributeMethods::Serialization::Type.new(col)
230
- end
231
- if create_time_zone_conversion_attribute?(name, col)
232
- columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(col)
233
- end
227
+ @serialized_column_names ||= self.columns_hash.keys.find_all do |name|
228
+ serialized_attributes.key?(name)
229
+ end
230
+
231
+ @serialized_column_names.each do |name|
232
+ columns_hash[name] = AttributeMethods::Serialization::Type.new(columns_hash[name])
233
+ end
234
+
235
+ @time_zone_column_names ||= self.columns_hash.find_all do |name, col|
236
+ create_time_zone_conversion_attribute?(name, col)
237
+ end.map!(&:first)
238
+
239
+ @time_zone_column_names.each do |name|
240
+ columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(columns_hash[name])
234
241
  end
235
242
 
236
243
  columns_hash
@@ -297,16 +304,19 @@ module ActiveRecord
297
304
  undefine_attribute_methods
298
305
  connection.schema_cache.clear_table_cache!(table_name) if table_exists?
299
306
 
300
- @arel_engine = nil
301
- @column_defaults = nil
302
- @column_names = nil
303
- @columns = nil
304
- @columns_hash = nil
305
- @column_types = nil
306
- @content_columns = nil
307
- @dynamic_methods_hash = nil
308
- @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
309
- @relation = nil
307
+ @arel_engine = nil
308
+ @column_defaults = nil
309
+ @column_names = nil
310
+ @columns = nil
311
+ @columns_hash = nil
312
+ @column_types = nil
313
+ @content_columns = nil
314
+ @dynamic_methods_hash = nil
315
+ @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
316
+ @relation = nil
317
+ @serialized_column_names = nil
318
+ @time_zone_column_names = nil
319
+ @cached_time_zone = nil
310
320
  end
311
321
 
312
322
  # This is a hook for use by modules that need to do extra stuff to
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  @records = []
7
7
  end
8
8
 
9
- def pluck(_column_name)
9
+ def pluck(*column_names)
10
10
  []
11
11
  end
12
12
 
@@ -42,10 +42,6 @@ module ActiveRecord
42
42
  @to_sql ||= ""
43
43
  end
44
44
 
45
- def where_values_hash
46
- {}
47
- end
48
-
49
45
  def count(*)
50
46
  0
51
47
  end
@@ -335,8 +335,17 @@ module ActiveRecord
335
335
 
336
336
  # Reloads the record from the database.
337
337
  #
338
- # This method modifies the receiver in-place. Attributes are updated, and
339
- # caches busted, in particular the associations cache.
338
+ # This method finds record by its primary key (which could be assigned manually) and
339
+ # modifies the receiver in-place:
340
+ #
341
+ # account = Account.new
342
+ # # => #<Account id: nil, email: nil>
343
+ # account.id = 1
344
+ # account.reload
345
+ # # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]]
346
+ # # => #<Account id: 1, email: 'account@example.com'>
347
+ #
348
+ # Attributes are updated, and caches busted, in particular the associations cache.
340
349
  #
341
350
  # If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
342
351
  # is raised. Otherwise, in addition to the in-place modification the method
@@ -383,14 +392,16 @@ module ActiveRecord
383
392
  end
384
393
 
385
394
  @attributes.update(fresh_object.instance_variable_get('@attributes'))
386
- @columns_hash = fresh_object.instance_variable_get('@columns_hash')
387
395
 
388
- @attributes_cache = {}
396
+ @column_types = self.class.column_types
397
+ @column_types_override = fresh_object.instance_variable_get('@column_types_override')
398
+ @attributes_cache = {}
389
399
  self
390
400
  end
391
401
 
392
402
  # Saves the record with the updated_at/on attributes set to the current time.
393
- # Please note that no validation is performed and no callbacks are executed.
403
+ # Please note that no validation is performed and only the +after_touch+
404
+ # callback is executed.
394
405
  # If an attribute name is passed, that attribute is updated along with
395
406
  # updated_at/on attributes.
396
407
  #
@@ -46,6 +46,7 @@ module ActiveRecord
46
46
  ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
47
47
  ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a
48
48
  ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures'
49
+ ActiveRecord::Tasks::DatabaseTasks.root = Rails.root
49
50
 
50
51
  if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
51
52
  if engine.paths['db/migrate'].existent
@@ -322,11 +322,14 @@ db_namespace = namespace :db do
322
322
  # desc "Recreate the test database from an existent schema.rb file"
323
323
  task :load_schema => 'db:test:purge' do
324
324
  begin
325
+ should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
325
326
  ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
326
327
  ActiveRecord::Schema.verbose = false
327
328
  db_namespace["schema:load"].invoke
328
329
  ensure
329
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
330
+ if should_reconnect
331
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
332
+ end
330
333
  end
331
334
  end
332
335
 
@@ -486,7 +486,7 @@ module ActiveRecord
486
486
  # Add to it the scope from this reflection (if any)
487
487
  scope_chain.first << scope if scope
488
488
 
489
- through_scope_chain = through_reflection.scope_chain
489
+ through_scope_chain = through_reflection.scope_chain.map(&:dup)
490
490
 
491
491
  if options[:source_type]
492
492
  through_scope_chain.first <<
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
18
18
 
19
19
  attr_reader :table, :klass, :loaded
20
- attr_accessor :default_scoped, :proxy_association
20
+ attr_accessor :default_scoped
21
21
  alias :model :klass
22
22
  alias :loaded? :loaded
23
23
  alias :default_scoped? :default_scoped
@@ -58,8 +58,8 @@ module ActiveRecord
58
58
 
59
59
  relation = self
60
60
 
61
- unless arel.orders.blank? && arel.taken.blank?
62
- ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
61
+ if logger && (arel.orders.present? || arel.taken.present?)
62
+ logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
63
63
  end
64
64
 
65
65
  start = options.delete(:start)
@@ -376,6 +376,7 @@ module ActiveRecord
376
376
  column ? column.type_cast(value) : value
377
377
  end
378
378
 
379
+ # TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
379
380
  def select_for_count
380
381
  if select_values.present?
381
382
  select = select_values.join(", ")
@@ -130,21 +130,21 @@ module ActiveRecord
130
130
  last or raise RecordNotFound
131
131
  end
132
132
 
133
- # Returns truthy if a record exists in the table that matches the +id+ or
134
- # conditions given, or falsy otherwise. The argument can take six forms:
133
+ # Returns +true+ if a record exists in the table that matches the +id+ or
134
+ # conditions given, or +false+ otherwise. The argument can take six forms:
135
135
  #
136
136
  # * Integer - Finds the record with this primary key.
137
137
  # * String - Finds the record with a primary key corresponding to this
138
138
  # string (such as <tt>'5'</tt>).
139
139
  # * Array - Finds the record that matches these +find+-style conditions
140
- # (such as <tt>['color = ?', 'red']</tt>).
140
+ # (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
141
141
  # * Hash - Finds the record that matches these +find+-style conditions
142
- # (such as <tt>{color: 'red'}</tt>).
142
+ # (such as <tt>{name: 'David'}</tt>).
143
143
  # * +false+ - Returns always +false+.
144
144
  # * No args - Returns +false+ if the table is empty, +true+ otherwise.
145
145
  #
146
- # For more information about specifying conditions as a Hash or Array,
147
- # see the Conditions section in the introduction to ActiveRecord::Base.
146
+ # For more information about specifying conditions as a hash or array,
147
+ # see the Conditions section in the introduction to <tt>ActiveRecord::Base</tt>.
148
148
  #
149
149
  # Note: You can't pass in a condition as a string (like <tt>name =
150
150
  # 'Jamie'</tt>), since it would be sanitized and then queried against
@@ -171,7 +171,7 @@ module ActiveRecord
171
171
  relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
172
172
  end
173
173
 
174
- connection.select_value(relation, "#{name} Exists", relation.bind_values)
174
+ connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
175
175
  rescue ThrowResult
176
176
  false
177
177
  end
@@ -222,7 +222,7 @@ module ActiveRecord
222
222
  end
223
223
 
224
224
  def construct_relation_for_association_find(join_dependency)
225
- relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
225
+ relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns + select_values)
226
226
  apply_join_dependency(relation, join_dependency)
227
227
  end
228
228
 
@@ -245,9 +245,9 @@ module ActiveRecord
245
245
 
246
246
  def construct_limited_ids_condition(relation)
247
247
  orders = relation.order_values.map { |val| val.presence }.compact
248
- values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
248
+ values = @klass.connection.columns_for_distinct("#{quoted_table_name}.#{quoted_primary_key}", orders)
249
249
 
250
- relation = relation.dup.select(values)
250
+ relation = relation.dup.select(values).distinct!
251
251
 
252
252
  id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
253
253
  ids_array = id_rows.map {|row| row[primary_key]}
@@ -62,7 +62,11 @@ module ActiveRecord
62
62
  def merge
63
63
  normal_values.each do |name|
64
64
  value = values[name]
65
- relation.send("#{name}!", *value) unless value.blank?
65
+ # The unless clause is here mostly for performance reasons (since the `send` call might be moderately
66
+ # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
67
+ # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
68
+ # don't fall through the cracks.
69
+ relation.send("#{name}!", *value) unless value.nil? || (value.blank? && false != value)
66
70
  end
67
71
 
68
72
  merge_multi_values
@@ -101,8 +105,15 @@ module ActiveRecord
101
105
  end
102
106
 
103
107
  def merge_multi_values
104
- relation.where_values = merged_wheres
105
- relation.bind_values = merged_binds
108
+ lhs_wheres = relation.where_values
109
+ rhs_wheres = values[:where] || []
110
+ lhs_binds = relation.bind_values
111
+ rhs_binds = values[:bind] || []
112
+
113
+ removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
114
+
115
+ relation.where_values = kept + rhs_wheres
116
+ relation.bind_values = filter_binds(lhs_binds, removed) + rhs_binds
106
117
 
107
118
  if values[:reordering]
108
119
  # override any order specified in the original relation
@@ -125,37 +136,29 @@ module ActiveRecord
125
136
  end
126
137
  end
127
138
 
128
- def merged_binds
129
- if values[:bind]
130
- (relation.bind_values + values[:bind]).uniq(&:first)
131
- else
132
- relation.bind_values
133
- end
139
+ def filter_binds(lhs_binds, removed_wheres)
140
+ set = Set.new removed_wheres.map { |x| x.left.name }
141
+ lhs_binds.dup.delete_if { |col,_| set.include? col.name }
134
142
  end
135
143
 
136
- def merged_wheres
137
- values[:where] ||= []
138
-
139
- if values[:where].empty? || relation.where_values.empty?
140
- relation.where_values + values[:where]
141
- else
142
- # Remove equalities from the existing relation with a LHS which is
143
- # present in the relation being merged in.
144
+ # Remove equalities from the existing relation with a LHS which is
145
+ # present in the relation being merged in.
146
+ # returns [things_to_remove, things_to_keep]
147
+ def partition_overwrites(lhs_wheres, rhs_wheres)
148
+ if lhs_wheres.empty? || rhs_wheres.empty?
149
+ return [[], lhs_wheres]
150
+ end
144
151
 
145
- seen = Set.new
146
- values[:where].each { |w|
147
- if w.respond_to?(:operator) && w.operator == :==
148
- seen << w.left
149
- end
150
- }
152
+ nodes = rhs_wheres.find_all do |w|
153
+ w.respond_to?(:operator) && w.operator == :==
154
+ end
155
+ seen = Set.new(nodes) { |node| node.left }
151
156
 
152
- relation.where_values.reject { |w|
153
- w.respond_to?(:operator) &&
154
- w.operator == :== &&
155
- seen.include?(w.left)
156
- } + values[:where]
157
+ lhs_wheres.partition do |w|
158
+ w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
157
159
  end
158
160
  end
161
+
159
162
  end
160
163
  end
161
164
  end
@@ -285,11 +285,13 @@ module ActiveRecord
285
285
  references!(references) if references.any?
286
286
 
287
287
  # if a symbol is given we prepend the quoted table name
288
- args = args.map { |arg|
289
- arg.is_a?(Symbol) ? "#{quoted_table_name}.#{arg} ASC" : arg
288
+ args = args.map! { |arg|
289
+ arg.is_a?(Symbol) ?
290
+ Arel::Nodes::Ascending.new(klass.arel_table[arg]) :
291
+ arg
290
292
  }
291
293
 
292
- self.order_values = args + self.order_values
294
+ self.order_values += args
293
295
  self
294
296
  end
295
297
 
@@ -301,7 +303,7 @@ module ActiveRecord
301
303
  #
302
304
  # User.order('email DESC').reorder('id ASC').order('name ASC')
303
305
  #
304
- # generates a query with 'ORDER BY name ASC, id ASC'.
306
+ # generates a query with 'ORDER BY id ASC, name ASC'.
305
307
  def reorder(*args)
306
308
  check_if_method_has_arguments!("reorder", args)
307
309
  spawn.reorder!(*args)
@@ -799,7 +801,7 @@ module ActiveRecord
799
801
  def build_arel
800
802
  arel = Arel::SelectManager.new(table.engine, table)
801
803
 
802
- build_joins(arel, joins_values) unless joins_values.empty?
804
+ build_joins(arel, joins_values.flatten) unless joins_values.empty?
803
805
 
804
806
  collapse_wheres(arel, (where_values - ['']).uniq)
805
807
 
@@ -875,14 +877,13 @@ module ActiveRecord
875
877
  end
876
878
 
877
879
  def collapse_wheres(arel, wheres)
878
- equalities = wheres.grep(Arel::Nodes::Equality)
879
-
880
- arel.where(Arel::Nodes::And.new(equalities)) unless equalities.empty?
881
-
882
- (wheres - equalities).each do |where|
880
+ predicates = wheres.map do |where|
881
+ next where if ::Arel::Nodes::Equality === where
883
882
  where = Arel.sql(where) if String === where
884
- arel.where(Arel::Nodes::Grouping.new(where))
883
+ Arel::Nodes::Grouping.new(where)
885
884
  end
885
+
886
+ arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
886
887
  end
887
888
 
888
889
  def build_where(opts, other = [])
@@ -907,6 +908,7 @@ module ActiveRecord
907
908
  case opts
908
909
  when Relation
909
910
  name ||= 'subquery'
911
+ self.bind_values = opts.bind_values + self.bind_values
910
912
  opts.arel.as(name.to_s)
911
913
  else
912
914
  opts