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
@@ -34,7 +34,9 @@ module ActiveRecord::Associations::Builder
34
34
  def belongs_to_counter_cache_before_destroy_for_#{name}
35
35
  unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
36
36
  record = #{name}
37
- record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
37
+ if record && !self.destroyed?
38
+ record.class.decrement_counter(:#{cache_column}, record.id)
39
+ end
38
40
  end
39
41
  end
40
42
 
@@ -68,7 +70,7 @@ module ActiveRecord::Associations::Builder
68
70
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
69
71
  def belongs_to_touch_after_save_or_destroy_for_#{name}
70
72
  foreign_key_field = #{reflection.foreign_key.inspect}
71
- old_foreign_id = attribute_was(foreign_key_field)
73
+ old_foreign_id = changed_attributes[foreign_key_field]
72
74
 
73
75
  if old_foreign_id
74
76
  klass = association(#{name.inspect}).klass
@@ -81,15 +81,15 @@ module ActiveRecord
81
81
  else
82
82
  if options[:finder_sql]
83
83
  find_by_scan(*args)
84
- elsif options[:inverse_of]
85
- args = args.flatten
86
- raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args.blank?
84
+ elsif options[:inverse_of] && loaded?
85
+ args_flatten = args.flatten
86
+ raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
87
87
 
88
88
  result = find_by_scan(*args)
89
89
 
90
90
  result_size = Array(result).size
91
- if !result || result_size != args.size
92
- scope.raise_record_not_found_exception!(args, result_size, args.size)
91
+ if !result || result_size != args_flatten.size
92
+ scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
93
93
  else
94
94
  result
95
95
  end
@@ -583,14 +583,14 @@ module ActiveRecord
583
583
  # specified, then #find scans the entire collection.
584
584
  def find_by_scan(*args)
585
585
  expects_array = args.first.kind_of?(Array)
586
- ids = args.flatten.compact.map{ |arg| arg.to_i }.uniq
586
+ ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
587
587
 
588
588
  if ids.size == 1
589
589
  id = ids.first
590
- record = load_target.detect { |r| id == r.id }
590
+ record = load_target.detect { |r| id == r.id.to_s }
591
591
  expects_array ? [ record ] : record
592
592
  else
593
- load_target.select { |r| ids.include?(r.id) }
593
+ load_target.select { |r| ids.include?(r.id.to_s) }
594
594
  end
595
595
  end
596
596
 
@@ -830,7 +830,7 @@ module ActiveRecord
830
830
  # person.pets.include?(Pet.find(20)) # => true
831
831
  # person.pets.include?(Pet.find(21)) # => false
832
832
  def include?(record)
833
- @association.include?(record)
833
+ !!@association.include?(record)
834
834
  end
835
835
 
836
836
  def proxy_association
@@ -847,9 +847,7 @@ module ActiveRecord
847
847
 
848
848
  # Returns a <tt>Relation</tt> object for the records in this association
849
849
  def scope
850
- @association.scope.tap do |scope|
851
- scope.proxy_association = @association
852
- end
850
+ @association.scope
853
851
  end
854
852
 
855
853
  # :nodoc:
@@ -32,6 +32,7 @@ module ActiveRecord
32
32
 
33
33
  def insert_record(record, validate = true, raise = false)
34
34
  set_owner_attributes(record)
35
+ set_inverse_instance(record)
35
36
 
36
37
  if raise
37
38
  record.save!(:validate => validate)
@@ -115,8 +116,7 @@ module ActiveRecord
115
116
  if records == :all
116
117
  scope = self.scope
117
118
  else
118
- keys = records.map { |r| r[reflection.association_primary_key] }
119
- scope = self.scope.where(reflection.association_primary_key => keys)
119
+ scope = self.scope.where(reflection.klass.primary_key => records)
120
120
  end
121
121
 
122
122
  if method == :delete_all
@@ -27,6 +27,8 @@ module ActiveRecord
27
27
 
28
28
  return self.target if !(target || record)
29
29
  if (target != record) || record.changed?
30
+ save &&= owner.persisted?
31
+
30
32
  transaction_if(save) do
31
33
  remove_target!(options[:dependent]) if target && !target.destroyed?
32
34
 
@@ -34,7 +36,7 @@ module ActiveRecord
34
36
  set_owner_attributes(record)
35
37
  set_inverse_instance(record)
36
38
 
37
- if owner.persisted? && save && !record.save
39
+ if save && !record.save
38
40
  nullify_owner_attributes(record)
39
41
  set_owner_attributes(target) if target
40
42
  raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
@@ -106,6 +106,8 @@ module ActiveRecord
106
106
  ]
107
107
  end
108
108
 
109
+ scope_chain_items += [reflection.klass.send(:build_default_scope)].compact
110
+
109
111
  scope_chain_items.each do |item|
110
112
  unless item.is_a?(Relation)
111
113
  item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item)
@@ -62,7 +62,20 @@ module ActiveRecord
62
62
  end
63
63
 
64
64
  def extract_record(row)
65
- Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
65
+ # This code is performance critical as it is called per row.
66
+ # see: https://github.com/rails/rails/pull/12185
67
+ hash = {}
68
+
69
+ index = 0
70
+ length = column_names_with_alias.length
71
+
72
+ while index < length
73
+ column_name, alias_name = column_names_with_alias[index]
74
+ hash[column_name] = row[alias_name]
75
+ index += 1
76
+ end
77
+
78
+ hash
66
79
  end
67
80
 
68
81
  def record_id(row)
@@ -141,10 +141,11 @@ module ActiveRecord
141
141
 
142
142
  def records_by_reflection(association)
143
143
  records.group_by do |record|
144
- reflection = record.class.reflections[association]
144
+ record_class = record.class
145
+ reflection = record_class.reflections[association]
145
146
 
146
147
  unless reflection
147
- raise ActiveRecord::ConfigurationError, "Association named '#{association}' was not found; " \
148
+ raise ActiveRecord::ConfigurationError, "Association named '#{association}' was not found on #{record_class.name}; " \
148
149
  "perhaps you misspelled it?"
149
150
  end
150
151
 
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/enumerable'
2
+ require 'mutex_m'
2
3
 
3
4
  module ActiveRecord
4
5
  # = Active Record Attribute Methods
@@ -7,6 +8,7 @@ module ActiveRecord
7
8
  include ActiveModel::AttributeMethods
8
9
 
9
10
  included do
11
+ initialize_generated_modules
10
12
  include Read
11
13
  include Write
12
14
  include BeforeTypeCast
@@ -18,12 +20,34 @@ module ActiveRecord
18
20
  end
19
21
 
20
22
  module ClassMethods
23
+ def inherited(child_class) #:nodoc:
24
+ child_class.initialize_generated_modules
25
+ super
26
+ end
27
+
28
+ def initialize_generated_modules # :nodoc:
29
+ @generated_attribute_methods = Module.new {
30
+ extend Mutex_m
31
+
32
+ const_set :AttrNames, Module.new {
33
+ def self.set_name_cache(name, value)
34
+ const_name = "ATTR_#{name}"
35
+ unless const_defined? const_name
36
+ const_set const_name, value.dup.freeze
37
+ end
38
+ end
39
+ }
40
+ }
41
+ @attribute_methods_generated = false
42
+ include @generated_attribute_methods
43
+ end
44
+
21
45
  # Generates all the attribute related methods for columns in the database
22
46
  # accessors, mutators and query methods.
23
47
  def define_attribute_methods # :nodoc:
24
48
  # Use a mutex; we don't want two thread simultaneously trying to define
25
49
  # attribute methods.
26
- @attribute_methods_mutex.synchronize do
50
+ generated_attribute_methods.synchronize do
27
51
  return if attribute_methods_generated?
28
52
  superclass.define_attribute_methods unless self == base_class
29
53
  super(column_names)
@@ -32,7 +56,7 @@ module ActiveRecord
32
56
  end
33
57
 
34
58
  def attribute_methods_generated? # :nodoc:
35
- @attribute_methods_generated ||= false
59
+ @attribute_methods_generated
36
60
  end
37
61
 
38
62
  def undefine_attribute_methods # :nodoc:
@@ -33,8 +33,11 @@ module ActiveRecord
33
33
  protected
34
34
 
35
35
  # We want to generate the methods via module_eval rather than
36
- # define_method, because define_method is slower on dispatch and
37
- # uses more memory (because it creates a closure).
36
+ # define_method, because define_method is slower on dispatch.
37
+ # Evaluating many similar methods may use more memory as the instruction
38
+ # sequences are duplicated and cached (in MRI). define_method may
39
+ # be slower on dispatch, but if you're careful about the closure
40
+ # created, then define_method will consume much less memory.
38
41
  #
39
42
  # But sometimes the database might return columns with
40
43
  # characters that are not allowed in normal method names (like
@@ -49,6 +52,8 @@ module ActiveRecord
49
52
  # key the @attributes_cache in read_attribute.
50
53
  def define_method_attribute(name)
51
54
  safe_name = name.unpack('h*').first
55
+ generated_attribute_methods::AttrNames.set_name_cache safe_name, name
56
+
52
57
  generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
53
58
  def __temp__#{safe_name}
54
59
  read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
@@ -77,13 +82,14 @@ module ActiveRecord
77
82
  # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
78
83
  name = attr_name.to_s
79
84
  @attributes_cache[name] || @attributes_cache.fetch(name) {
80
- column = @columns_hash.fetch(name) {
81
- return @attributes.fetch(name) {
82
- if name == 'id' && self.class.primary_key != name
83
- read_attribute(self.class.primary_key)
84
- end
85
- }
86
- }
85
+ column = @column_types_override[name] if @column_types_override
86
+ column ||= @column_types[name]
87
+
88
+ return @attributes.fetch(name) {
89
+ if name == 'id' && self.class.primary_key != name
90
+ read_attribute(self.class.primary_key)
91
+ end
92
+ } unless column
87
93
 
88
94
  value = @attributes.fetch(name) {
89
95
  return block_given? ? yield(name) : nil
@@ -51,7 +51,7 @@ module ActiveRecord
51
51
  def create_time_zone_conversion_attribute?(name, column)
52
52
  time_zone_aware_attributes &&
53
53
  !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
54
- [:datetime, :timestamp].include?(column.type)
54
+ (:datetime == column.type || :timestamp == column.type)
55
55
  end
56
56
  end
57
57
  end
@@ -14,6 +14,8 @@ module ActiveRecord
14
14
  # this code.
15
15
  def define_method_attribute=(name)
16
16
  safe_name = name.unpack('h*').first
17
+ generated_attribute_methods::AttrNames.set_name_cache safe_name, name
18
+
17
19
  generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
18
20
  def __temp__#{safe_name}=(value)
19
21
  write_attribute(AttrNames::ATTR_#{safe_name}, value)
@@ -335,15 +335,19 @@ module ActiveRecord
335
335
  autosave = reflection.options[:autosave]
336
336
 
337
337
  if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
338
- records_to_destroy = []
338
+
339
+ if autosave
340
+ records_to_destroy = records.select(&:marked_for_destruction?)
341
+ records_to_destroy.each { |record| association.destroy(record) }
342
+ records -= records_to_destroy
343
+ end
344
+
339
345
  records.each do |record|
340
346
  next if record.destroyed?
341
347
 
342
348
  saved = true
343
349
 
344
- if autosave && record.marked_for_destruction?
345
- records_to_destroy << record
346
- elsif autosave != false && (@new_record_before_save || record.new_record?)
350
+ if autosave != false && (@new_record_before_save || record.new_record?)
347
351
  if autosave
348
352
  saved = association.insert_record(record, false)
349
353
  else
@@ -355,10 +359,6 @@ module ActiveRecord
355
359
 
356
360
  raise ActiveRecord::Rollback unless saved
357
361
  end
358
-
359
- records_to_destroy.each do |record|
360
- association.destroy(record)
361
- end
362
362
  end
363
363
 
364
364
  # reconstruct the scope now that we know the owner's id
@@ -23,11 +23,14 @@ module ActiveRecord
23
23
  # Check out <tt>ActiveRecord::Transactions</tt> for more details about <tt>after_commit</tt> and
24
24
  # <tt>after_rollback</tt>.
25
25
  #
26
+ # Additionally, an <tt>after_touch</tt> callback is triggered whenever an
27
+ # object is touched.
28
+ #
26
29
  # Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
27
30
  # is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
28
31
  # are instantiated as well.
29
32
  #
30
- # That's a total of twelve callbacks, which gives you immense power to react and prepare for each state in the
33
+ # There are nineteen callbacks in total, which give you immense power to react and prepare for each state in the
31
34
  # Active Record life cycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar,
32
35
  # except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
33
36
  #
@@ -377,14 +377,14 @@ module ActiveRecord
377
377
  update_sql(sql, name)
378
378
  end
379
379
 
380
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
381
- [sql, binds]
382
- end
380
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
381
+ [sql, binds]
382
+ end
383
383
 
384
- def last_inserted_id(result)
385
- row = result.rows.first
386
- row && row.first
387
- end
384
+ def last_inserted_id(result)
385
+ row = result.rows.first
386
+ row && row.first
387
+ end
388
388
  end
389
389
  end
390
390
  end
@@ -706,12 +706,21 @@ module ActiveRecord
706
706
  end
707
707
 
708
708
  # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
709
- # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
710
709
  #
711
- # distinct("posts.id", "posts.created_at desc")
710
+ # distinct("posts.id", ["posts.created_at desc"])
712
711
  #
713
712
  def distinct(columns, order_by)
714
- "DISTINCT #{columns}"
713
+ ActiveSupport::Deprecation.warn("#distinct is deprecated and shall be removed from future releases.")
714
+ "DISTINCT #{columns_for_distinct(columns, order_by)}"
715
+ end
716
+
717
+ # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
718
+ # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they
719
+ # require the order columns appear in the SELECT.
720
+ #
721
+ # columns_for_distinct("posts.id", ["posts.created_at desc"])
722
+ def columns_for_distinct(columns, orders) # :nodoc:
723
+ columns
715
724
  end
716
725
 
717
726
  # Adds timestamps (+created_at+ and +updated_at+) columns to the named table.
@@ -99,6 +99,7 @@ module ActiveRecord
99
99
  @query_cache_enabled = false
100
100
  @schema_cache = SchemaCache.new self
101
101
  @visitor = nil
102
+ @prepared_statements = false
102
103
  end
103
104
 
104
105
  def valid_type?(type)
@@ -197,10 +198,11 @@ module ActiveRecord
197
198
  end
198
199
 
199
200
  def unprepared_statement
200
- old, @visitor = @visitor, unprepared_visitor
201
+ old_prepared_statements, @prepared_statements = @prepared_statements, false
202
+ old_visitor, @visitor = @visitor, unprepared_visitor
201
203
  yield
202
204
  ensure
203
- @visitor = old
205
+ @visitor, @prepared_statements = old_visitor, old_prepared_statements
204
206
  end
205
207
 
206
208
  # Returns the human-readable name of the adapter. Use mixed case - one
@@ -280,6 +282,14 @@ module ActiveRecord
280
282
  false
281
283
  end
282
284
 
285
+ # This is meant to be implemented by the adapters that support extensions
286
+ def disable_extension(name)
287
+ end
288
+
289
+ # This is meant to be implemented by the adapters that support extensions
290
+ def enable_extension(name)
291
+ end
292
+
283
293
  # A list of extensions, to be filled in by adapters that support them. At
284
294
  # the moment only postgresql does.
285
295
  def extensions
@@ -435,6 +445,10 @@ module ActiveRecord
435
445
  # override in derived class
436
446
  ActiveRecord::StatementInvalid.new(message, exception)
437
447
  end
448
+
449
+ def without_prepared_statement?(binds)
450
+ !@prepared_statements || binds.empty?
451
+ end
438
452
  end
439
453
  end
440
454
  end
@@ -165,6 +165,7 @@ module ActiveRecord
165
165
  @quoted_column_names, @quoted_table_names = {}, {}
166
166
 
167
167
  if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
168
+ @prepared_statements = true
168
169
  @visitor = Arel::Visitors::MySQL.new self
169
170
  else
170
171
  @visitor = unprepared_visitor
@@ -460,7 +461,8 @@ module ActiveRecord
460
461
  sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
461
462
  execute_and_free(sql, 'SCHEMA') do |result|
462
463
  each_hash(result).map do |field|
463
- new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
464
+ field_name = set_field_encoding(field[:Field])
465
+ new_column(field_name, field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
464
466
  end
465
467
  end
466
468
  end