activerecord 4.0.0.beta1 → 4.0.0.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +573 -30
  3. data/README.rdoc +3 -3
  4. data/lib/active_record.rb +8 -2
  5. data/lib/active_record/associations.rb +16 -9
  6. data/lib/active_record/associations/association.rb +8 -6
  7. data/lib/active_record/associations/association_scope.rb +2 -1
  8. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  10. data/lib/active_record/associations/builder/belongs_to.rb +37 -5
  11. data/lib/active_record/associations/collection_association.rb +38 -14
  12. data/lib/active_record/associations/collection_proxy.rb +18 -15
  13. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +3 -3
  14. data/lib/active_record/associations/has_many_association.rb +4 -3
  15. data/lib/active_record/associations/has_many_through_association.rb +1 -1
  16. data/lib/active_record/associations/has_one_association.rb +1 -1
  17. data/lib/active_record/associations/join_dependency.rb +29 -8
  18. data/lib/active_record/associations/join_dependency/join_association.rb +26 -6
  19. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  20. data/lib/active_record/associations/join_dependency/join_part.rb +6 -6
  21. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
  22. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  23. data/lib/active_record/associations/through_association.rb +1 -1
  24. data/lib/active_record/attribute_assignment.rb +5 -5
  25. data/lib/active_record/attribute_methods.rb +20 -5
  26. data/lib/active_record/attribute_methods/dirty.rb +5 -1
  27. data/lib/active_record/attribute_methods/primary_key.rb +1 -1
  28. data/lib/active_record/attribute_methods/serialization.rb +9 -2
  29. data/lib/active_record/autosave_association.rb +19 -5
  30. data/lib/active_record/base.rb +3 -3
  31. data/lib/active_record/callbacks.rb +1 -1
  32. data/lib/active_record/coders/yaml_column.rb +8 -13
  33. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +3 -9
  34. data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -1
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -1
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +2 -8
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +60 -61
  38. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +13 -2
  39. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +291 -153
  40. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract_adapter.rb +92 -1
  42. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +55 -29
  43. data/lib/active_record/connection_adapters/column.rb +4 -4
  44. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  45. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -3
  46. data/lib/active_record/connection_adapters/mysql_adapter.rb +5 -5
  47. data/lib/active_record/connection_adapters/postgresql/cast.rb +22 -2
  48. data/lib/active_record/connection_adapters/postgresql/oid.rb +25 -6
  49. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -13
  50. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +50 -9
  51. data/lib/active_record/connection_adapters/postgresql_adapter.rb +53 -24
  52. data/lib/active_record/connection_adapters/schema_cache.rb +35 -7
  53. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +13 -5
  54. data/lib/active_record/connection_handling.rb +7 -7
  55. data/lib/active_record/core.rb +43 -8
  56. data/lib/active_record/counter_cache.rb +2 -1
  57. data/lib/active_record/errors.rb +11 -10
  58. data/lib/active_record/explain.rb +9 -7
  59. data/lib/active_record/explain_registry.rb +30 -0
  60. data/lib/active_record/explain_subscriber.rb +3 -2
  61. data/lib/active_record/fixture_set/file.rb +1 -2
  62. data/lib/active_record/fixtures.rb +13 -7
  63. data/lib/active_record/inheritance.rb +12 -4
  64. data/lib/active_record/integration.rb +3 -3
  65. data/lib/active_record/locking/optimistic.rb +2 -2
  66. data/lib/active_record/log_subscriber.rb +2 -2
  67. data/lib/active_record/migration.rb +69 -21
  68. data/lib/active_record/model_schema.rb +1 -1
  69. data/lib/active_record/nested_attributes.rb +98 -46
  70. data/lib/active_record/persistence.rb +3 -3
  71. data/lib/active_record/querying.rb +1 -1
  72. data/lib/active_record/railtie.rb +18 -4
  73. data/lib/active_record/railties/console_sandbox.rb +3 -2
  74. data/lib/active_record/railties/controller_runtime.rb +2 -1
  75. data/lib/active_record/railties/databases.rake +38 -80
  76. data/lib/active_record/reflection.rb +36 -3
  77. data/lib/active_record/relation.rb +18 -8
  78. data/lib/active_record/relation/calculations.rb +10 -5
  79. data/lib/active_record/relation/delegation.rb +3 -5
  80. data/lib/active_record/relation/finder_methods.rb +27 -14
  81. data/lib/active_record/relation/merger.rb +30 -2
  82. data/lib/active_record/relation/predicate_builder.rb +1 -6
  83. data/lib/active_record/relation/query_methods.rb +113 -16
  84. data/lib/active_record/runtime_registry.rb +17 -0
  85. data/lib/active_record/schema_dumper.rb +5 -1
  86. data/lib/active_record/schema_migration.rb +8 -5
  87. data/lib/active_record/scoping.rb +56 -2
  88. data/lib/active_record/scoping/default.rb +12 -11
  89. data/lib/active_record/scoping/named.rb +7 -3
  90. data/lib/active_record/serialization.rb +1 -1
  91. data/lib/active_record/statement_cache.rb +26 -0
  92. data/lib/active_record/tasks/database_tasks.rb +55 -10
  93. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  94. data/lib/active_record/tasks/mysql_database_tasks.rb +7 -2
  95. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  96. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  97. data/lib/active_record/timestamp.rb +6 -0
  98. data/lib/active_record/transactions.rb +7 -3
  99. data/lib/active_record/validations.rb +1 -2
  100. data/lib/active_record/validations/uniqueness.rb +7 -3
  101. data/lib/active_record/version.rb +7 -6
  102. data/lib/rails/generators/active_record/migration/migration_generator.rb +9 -2
  103. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  104. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  105. data/lib/rails/generators/active_record/model/templates/model.rb +4 -1
  106. metadata +17 -12
  107. data/examples/associations.png +0 -0
data/README.rdoc CHANGED
@@ -130,7 +130,7 @@ A short rundown of some of the major features:
130
130
  SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
131
131
 
132
132
 
133
- * Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
133
+ * Logging support for Log4r[http://log4r.rubyforge.org] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
134
134
 
135
135
  ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
136
136
  ActiveRecord::Base.logger = Log4r::Logger.new('Application Log')
@@ -190,7 +190,7 @@ The latest version of Active Record can be installed with RubyGems:
190
190
 
191
191
  % [sudo] gem install activerecord
192
192
 
193
- Source code can be downloaded as part of the Rails project on GitHub
193
+ Source code can be downloaded as part of the Rails project on GitHub:
194
194
 
195
195
  * https://github.com/rails/rails/tree/master/activerecord
196
196
 
@@ -204,7 +204,7 @@ Active Record is released under the MIT license:
204
204
 
205
205
  == Support
206
206
 
207
- API documentation is at
207
+ API documentation is at:
208
208
 
209
209
  * http://api.rubyonrails.org
210
210
 
data/lib/active_record.rb CHANGED
@@ -35,8 +35,8 @@ module ActiveRecord
35
35
  autoload :Base
36
36
  autoload :Callbacks
37
37
  autoload :Core
38
- autoload :CounterCache
39
38
  autoload :ConnectionHandling
39
+ autoload :CounterCache
40
40
  autoload :DynamicMatchers
41
41
  autoload :Explain
42
42
  autoload :Inheritance
@@ -50,12 +50,14 @@ module ActiveRecord
50
50
  autoload :Querying
51
51
  autoload :ReadonlyAttributes
52
52
  autoload :Reflection
53
+ autoload :RuntimeRegistry
53
54
  autoload :Sanitization
54
55
  autoload :Schema
55
56
  autoload :SchemaDumper
56
57
  autoload :SchemaMigration
57
58
  autoload :Scoping
58
59
  autoload :Serialization
60
+ autoload :StatementCache
59
61
  autoload :Store
60
62
  autoload :Timestamp
61
63
  autoload :Transactions
@@ -69,8 +71,8 @@ module ActiveRecord
69
71
 
70
72
  autoload :Aggregations
71
73
  autoload :Associations
72
- autoload :AttributeMethods
73
74
  autoload :AttributeAssignment
75
+ autoload :AttributeMethods
74
76
  autoload :AutosaveAssociation
75
77
 
76
78
  autoload :Relation
@@ -143,6 +145,10 @@ module ActiveRecord
143
145
  autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
144
146
  autoload :PostgreSQLDatabaseTasks,
145
147
  'active_record/tasks/postgresql_database_tasks'
148
+
149
+ autoload :FirebirdDatabaseTasks, 'active_record/tasks/firebird_database_tasks'
150
+ autoload :SqlserverDatabaseTasks, 'active_record/tasks/sqlserver_database_tasks'
151
+ autoload :OracleDatabaseTasks, 'active_record/tasks/oracle_database_tasks'
146
152
  end
147
153
 
148
154
  autoload :TestCase
@@ -241,6 +241,7 @@ module ActiveRecord
241
241
  # others.destroy_all | X | X | X
242
242
  # others.find(*args) | X | X | X
243
243
  # others.exists? | X | X | X
244
+ # others.distinct | X | X | X
244
245
  # others.uniq | X | X | X
245
246
  # others.reset | X | X | X
246
247
  #
@@ -965,7 +966,7 @@ module ActiveRecord
965
966
  # For +has_and_belongs_to_many+, <tt>delete</tt> and <tt>destroy</tt> are the same: they
966
967
  # cause the records in the join table to be removed.
967
968
  #
968
- # For +has_many+, <tt>destroy</tt> and <tt>destory_all</tt> will always call the <tt>destroy</tt> method of the
969
+ # For +has_many+, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the
969
970
  # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
970
971
  # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
971
972
  # if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
@@ -987,7 +988,7 @@ module ActiveRecord
987
988
  # associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+
988
989
  # <tt>:through</tt>, the join records will be deleted, but the associated records won't.
989
990
  #
990
- # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by_name('food'))</tt>
991
+ # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
991
992
  # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
992
993
  # to be removed from the database.
993
994
  #
@@ -1024,7 +1025,7 @@ module ActiveRecord
1024
1025
  # [collection<<(object, ...)]
1025
1026
  # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
1026
1027
  # Note that this operation instantly fires update sql without waiting for the save or update call on the
1027
- # parent object.
1028
+ # parent object, unless the parent object is a new record.
1028
1029
  # [collection.delete(object, ...)]
1029
1030
  # Removes one or more objects from the collection by setting their foreign keys to +NULL+.
1030
1031
  # Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>,
@@ -1072,6 +1073,9 @@ module ActiveRecord
1072
1073
  # with +attributes+, linked to this object through a foreign key, and that has already
1073
1074
  # been saved (if it passed the validation). *Note*: This only works if the base model
1074
1075
  # already exists in the DB, not if it is a new (unsaved) record!
1076
+ # [collection.create!(attributes = {})]
1077
+ # Does the same as <tt>collection.create</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
1078
+ # if the record is invalid.
1075
1079
  #
1076
1080
  # (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
1077
1081
  # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
@@ -1093,6 +1097,7 @@ module ActiveRecord
1093
1097
  # * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
1094
1098
  # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
1095
1099
  # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
1100
+ # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
1096
1101
  # The declaration can also include an options hash to specialize the behavior of the association.
1097
1102
  #
1098
1103
  # === Options
@@ -1114,11 +1119,11 @@ module ActiveRecord
1114
1119
  # similar callbacks may affect the :dependent behavior, and the
1115
1120
  # :dependent behavior may affect other callbacks.
1116
1121
  #
1117
- # * <tt>:destroy</tt> causes all the associated objects to also be destroyed
1118
- # * <tt>:delete_all</tt> causes all the asssociated objects to be deleted directly from the database (so callbacks will not execute)
1122
+ # * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
1123
+ # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
1119
1124
  # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
1120
- # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records
1121
- # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects
1125
+ # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records.
1126
+ # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
1122
1127
  #
1123
1128
  # If using with the <tt>:through</tt> option, the association on the join model must be
1124
1129
  # a +belongs_to+, and the records which get deleted are the join records, rather than
@@ -1231,7 +1236,7 @@ module ActiveRecord
1231
1236
  # its owner is destroyed:
1232
1237
  #
1233
1238
  # * <tt>:destroy</tt> causes the associated object to also be destroyed
1234
- # * <tt>:delete</tt> causes the asssociated object to be deleted directly from the database (so callbacks will not execute)
1239
+ # * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
1235
1240
  # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
1236
1241
  # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
1237
1242
  # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
@@ -1407,6 +1412,8 @@ module ActiveRecord
1407
1412
  # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
1408
1413
  # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
1409
1414
  # custom <tt>:join_table</tt> option if you need to.
1415
+ # If your tables share a common prefix, it will only appear once at the beginning. For example,
1416
+ # the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products".
1410
1417
  #
1411
1418
  # The join table should not have a primary key or a model associated with it. You must manually generate the
1412
1419
  # join table with a migration such as this:
@@ -1433,7 +1440,7 @@ module ActiveRecord
1433
1440
  # Adds one or more objects to the collection by creating associations in the join table
1434
1441
  # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
1435
1442
  # Note that this operation instantly fires update sql without waiting for the save or update call on the
1436
- # parent object.
1443
+ # parent object, unless the parent object is a new record.
1437
1444
  # [collection.delete(object, ...)]
1438
1445
  # Removes one or more objects from the collection by removing their associations from the join table.
1439
1446
  # This does not destroy the objects.
@@ -30,7 +30,7 @@ module ActiveRecord
30
30
  reset_scope
31
31
  end
32
32
 
33
- # Returns the name of the table of the related class:
33
+ # Returns the name of the table of the associated class:
34
34
  #
35
35
  # post.comments.aliased_table_name # => "comments"
36
36
  #
@@ -92,7 +92,7 @@ module ActiveRecord
92
92
  # The scope for this association.
93
93
  #
94
94
  # Note that the association_scope is merged into the target_scope only when the
95
- # scoped method is called. This is because at that point the call may be surrounded
95
+ # scope method is called. This is because at that point the call may be surrounded
96
96
  # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
97
97
  # actually gets built.
98
98
  def association_scope
@@ -113,7 +113,7 @@ module ActiveRecord
113
113
  end
114
114
  end
115
115
 
116
- # This class of the target. belongs_to polymorphic overrides this to look at the
116
+ # Returns the class of the target. belongs_to polymorphic overrides this to look at the
117
117
  # polymorphic_type field on the owner.
118
118
  def klass
119
119
  reflection.klass
@@ -146,7 +146,7 @@ module ActiveRecord
146
146
 
147
147
  def interpolate(sql, record = nil)
148
148
  if sql.respond_to?(:to_proc)
149
- owner.send(:instance_exec, record, &sql)
149
+ owner.instance_exec(record, &sql)
150
150
  else
151
151
  sql
152
152
  end
@@ -203,7 +203,7 @@ module ActiveRecord
203
203
  # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
204
204
  # the kind of the class of the associated objects. Meant to be used as
205
205
  # a sanity check when you are about to assign an associated record.
206
- def raise_on_type_mismatch(record)
206
+ def raise_on_type_mismatch!(record)
207
207
  unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
208
208
  message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
209
209
  raise ActiveRecord::AssociationTypeMismatch, message
@@ -217,7 +217,8 @@ module ActiveRecord
217
217
  reflection.inverse_of
218
218
  end
219
219
 
220
- # Is this association invertible? Can be redefined by subclasses.
220
+ # Returns true if inverse association on the given record needs to be set.
221
+ # This method is redefined by subclasses.
221
222
  def invertible_for?(record)
222
223
  inverse_reflection_for(record)
223
224
  end
@@ -235,6 +236,7 @@ module ActiveRecord
235
236
  skip_assign = [reflection.foreign_key, reflection.type].compact
236
237
  attributes = create_scope.except(*(record.changed - skip_assign))
237
238
  record.assign_attributes(attributes)
239
+ set_inverse_instance(record)
238
240
  end
239
241
  end
240
242
  end
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  private
23
23
 
24
24
  def column_for(table_name, column_name)
25
- columns = alias_tracker.connection.schema_cache.columns_hash[table_name]
25
+ columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
26
26
  columns[column_name]
27
27
  end
28
28
 
@@ -101,6 +101,7 @@ module ActiveRecord
101
101
 
102
102
  scope.includes! item.includes_values
103
103
  scope.where_values += item.where_values
104
+ scope.order_values |= item.order_values
104
105
  end
105
106
  end
106
107
 
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
- # = Active Record Belongs To Associations
2
+ # = Active Record Belongs To Association
3
3
  module Associations
4
4
  class BelongsToAssociation < SingularAssociation #:nodoc:
5
5
 
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  end
9
9
 
10
10
  def replace(record)
11
- raise_on_type_mismatch(record) if record
11
+ raise_on_type_mismatch!(record) if record
12
12
 
13
13
  update_counters(record)
14
14
  replace_keys(record)
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  reflection.polymorphic_inverse_of(record.class)
23
23
  end
24
24
 
25
- def raise_on_type_mismatch(record)
25
+ def raise_on_type_mismatch!(record)
26
26
  # A polymorphic association cannot have a type mismatch, by definition
27
27
  end
28
28
 
@@ -21,23 +21,44 @@ module ActiveRecord::Associations::Builder
21
21
 
22
22
  def add_counter_cache_callbacks(reflection)
23
23
  cache_column = reflection.counter_cache_column
24
+ foreign_key = reflection.foreign_key
24
25
 
25
26
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
26
27
  def belongs_to_counter_cache_after_create_for_#{name}
27
- record = #{name}
28
- record.class.increment_counter(:#{cache_column}, record.id) unless record.nil?
28
+ if record = #{name}
29
+ record.class.increment_counter(:#{cache_column}, record.id)
30
+ @_after_create_counter_called = true
31
+ end
29
32
  end
30
33
 
31
34
  def belongs_to_counter_cache_before_destroy_for_#{name}
32
- unless marked_for_destruction?
35
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
33
36
  record = #{name}
34
37
  record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
35
38
  end
36
39
  end
40
+
41
+ def belongs_to_counter_cache_after_update_for_#{name}
42
+ if (@_after_create_counter_called ||= false)
43
+ @_after_create_counter_called = false
44
+ elsif self.#{foreign_key}_changed? && !new_record? && defined?(#{name.to_s.camelize})
45
+ model = #{name.to_s.camelize}
46
+ foreign_key_was = self.#{foreign_key}_was
47
+ foreign_key = self.#{foreign_key}
48
+
49
+ if foreign_key && model.respond_to?(:increment_counter)
50
+ model.increment_counter(:#{cache_column}, foreign_key)
51
+ end
52
+ if foreign_key_was && model.respond_to?(:decrement_counter)
53
+ model.decrement_counter(:#{cache_column}, foreign_key_was)
54
+ end
55
+ end
56
+ end
37
57
  CODE
38
58
 
39
59
  model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
40
60
  model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
61
+ model.after_update "belongs_to_counter_cache_after_update_for_#{name}"
41
62
 
42
63
  klass = reflection.class_name.safe_constantize
43
64
  klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
@@ -46,9 +67,20 @@ module ActiveRecord::Associations::Builder
46
67
  def add_touch_callbacks(reflection)
47
68
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
48
69
  def belongs_to_touch_after_save_or_destroy_for_#{name}
49
- record = #{name}
70
+ foreign_key_field = #{reflection.foreign_key.inspect}
71
+ old_foreign_id = attribute_was(foreign_key_field)
72
+
73
+ if old_foreign_id
74
+ klass = association(#{name.inspect}).klass
75
+ old_record = klass.find_by(klass.primary_key => old_foreign_id)
50
76
 
51
- unless record.nil?
77
+ if old_record
78
+ old_record.touch #{options[:touch].inspect if options[:touch] != true}
79
+ end
80
+ end
81
+
82
+ record = #{name}
83
+ unless record.nil? || record.new_record?
52
84
  record.touch #{options[:touch].inspect if options[:touch] != true}
53
85
  end
54
86
  end
@@ -34,7 +34,7 @@ module ActiveRecord
34
34
  reload
35
35
  end
36
36
 
37
- CollectionProxy.new(klass, self)
37
+ @proxy ||= CollectionProxy.new(klass, self)
38
38
  end
39
39
 
40
40
  # Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -81,6 +81,18 @@ 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?
87
+
88
+ result = find_by_scan(*args)
89
+
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)
93
+ else
94
+ result
95
+ end
84
96
  else
85
97
  scope.find(*args)
86
98
  end
@@ -174,13 +186,14 @@ module ActiveRecord
174
186
 
175
187
  reflection.klass.count_by_sql(custom_counter_sql)
176
188
  else
177
- if association_scope.uniq_value
189
+ relation = scope
190
+ if association_scope.distinct_value
178
191
  # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
179
192
  column_name ||= reflection.klass.primary_key
180
- count_options[:distinct] = true
193
+ relation = relation.distinct
181
194
  end
182
195
 
183
- value = scope.count(column_name, count_options)
196
+ value = relation.count(column_name)
184
197
 
185
198
  limit = options[:limit]
186
199
  offset = options[:offset]
@@ -204,6 +217,15 @@ module ActiveRecord
204
217
  dependent = options[:dependent]
205
218
 
206
219
  if records.first == :all
220
+
221
+ if dependent && dependent == :destroy
222
+ message = 'In Rails 4.1 delete_all on associations would not fire callbacks. ' \
223
+ 'It means if the :dependent option is :destroy then the associated ' \
224
+ 'records would be deleted without loading and invoking callbacks.'
225
+
226
+ ActiveRecord::Base.logger ? ActiveRecord::Base.logger.warn(message) : $stderr.puts(message)
227
+ end
228
+
207
229
  if loaded? || dependent == :destroy
208
230
  delete_or_destroy(load_target, dependent)
209
231
  else
@@ -237,14 +259,14 @@ module ActiveRecord
237
259
  # +count_records+, which is a method descendants have to provide.
238
260
  def size
239
261
  if !find_target? || loaded?
240
- if association_scope.uniq_value
262
+ if association_scope.distinct_value
241
263
  target.uniq.size
242
264
  else
243
265
  target.size
244
266
  end
245
267
  elsif !loaded? && !association_scope.group_values.empty?
246
268
  load_target.size
247
- elsif !loaded? && !association_scope.uniq_value && target.is_a?(Array)
269
+ elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
248
270
  unsaved_records = target.select { |r| r.new_record? }
249
271
  unsaved_records.size + count_records
250
272
  else
@@ -297,17 +319,18 @@ module ActiveRecord
297
319
  end
298
320
  end
299
321
 
300
- def uniq
322
+ def distinct
301
323
  seen = {}
302
324
  load_target.find_all do |record|
303
325
  seen[record.id] = true unless seen.key?(record.id)
304
326
  end
305
327
  end
328
+ alias uniq distinct
306
329
 
307
330
  # Replace this collection with +other_array+. This will perform a diff
308
331
  # and delete/add only records that have changed.
309
332
  def replace(other_array)
310
- other_array.each { |val| raise_on_type_mismatch(val) }
333
+ other_array.each { |val| raise_on_type_mismatch!(val) }
311
334
  original_target = load_target.dup
312
335
 
313
336
  if owner.new_record?
@@ -343,7 +366,7 @@ module ActiveRecord
343
366
  callback(:before_add, record)
344
367
  yield(record) if block_given?
345
368
 
346
- if association_scope.uniq_value && index = @target.index(record)
369
+ if association_scope.distinct_value && index = @target.index(record)
347
370
  @target[index] = record
348
371
  else
349
372
  @target << record
@@ -454,7 +477,7 @@ module ActiveRecord
454
477
 
455
478
  def delete_or_destroy(records, method)
456
479
  records = records.flatten
457
- records.each { |record| raise_on_type_mismatch(record) }
480
+ records.each { |record| raise_on_type_mismatch!(record) }
458
481
  existing_records = records.reject { |r| r.new_record? }
459
482
 
460
483
  if existing_records.empty?
@@ -495,9 +518,9 @@ module ActiveRecord
495
518
  result = true
496
519
 
497
520
  records.flatten.each do |record|
498
- raise_on_type_mismatch(record)
499
- add_to_target(record) do |r|
500
- result &&= insert_record(record) unless owner.new_record?
521
+ raise_on_type_mismatch!(record)
522
+ add_to_target(record) do |rec|
523
+ result &&= insert_record(rec) unless owner.new_record?
501
524
  end
502
525
  end
503
526
 
@@ -556,7 +579,8 @@ module ActiveRecord
556
579
  end
557
580
  end
558
581
 
559
- # If using a custom finder_sql, #find scans the entire collection.
582
+ # If using a custom finder_sql or if the :inverse_of option has been
583
+ # specified, then #find scans the entire collection.
560
584
  def find_by_scan(*args)
561
585
  expects_array = args.first.kind_of?(Array)
562
586
  ids = args.flatten.compact.map{ |arg| arg.to_i }.uniq