activerecord 3.2.22.4 → 4.0.13

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  attr_reader :association, :alias_tracker
7
7
 
8
8
  delegate :klass, :owner, :reflection, :interpolate, :to => :association
9
- delegate :chain, :conditions, :options, :source_options, :active_record, :to => :reflection
9
+ delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection
10
10
 
11
11
  def initialize(association)
12
12
  @association = association
@@ -15,23 +15,28 @@ module ActiveRecord
15
15
 
16
16
  def scope
17
17
  scope = klass.unscoped
18
- scope = scope.extending(*Array.wrap(options[:extend]))
19
-
20
- # It's okay to just apply all these like this. The options will only be present if the
21
- # association supports that option; this is enforced by the association builder.
22
- scope = scope.apply_finder_options(options.slice(
23
- :readonly, :include, :order, :limit, :joins, :group, :having, :offset, :select))
18
+ scope.extending! Array(options[:extend])
19
+ add_constraints(scope)
20
+ end
24
21
 
25
- if options[:through] && !options[:include]
26
- scope = scope.includes(source_options[:include])
27
- end
22
+ private
28
23
 
29
- scope = scope.uniq if options[:uniq]
24
+ def column_for(table_name, column_name)
25
+ columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
26
+ columns[column_name]
27
+ end
30
28
 
31
- add_constraints(scope)
29
+ def bind_value(scope, column, value)
30
+ substitute = alias_tracker.connection.substitute_at(
31
+ column, scope.bind_values.length)
32
+ scope.bind_values += [[column, value]]
33
+ substitute
32
34
  end
33
35
 
34
- private
36
+ def bind(scope, table_name, column_name, value)
37
+ column = column_for table_name, column_name
38
+ bind_value scope, column, value
39
+ end
35
40
 
36
41
  def add_constraints(scope)
37
42
  tables = construct_tables
@@ -53,7 +58,7 @@ module ActiveRecord
53
58
 
54
59
  if reflection.source_macro == :belongs_to
55
60
  if reflection.options[:polymorphic]
56
- key = reflection.association_primary_key(klass)
61
+ key = reflection.association_primary_key(self.klass)
57
62
  else
58
63
  key = reflection.association_primary_key
59
64
  end
@@ -64,21 +69,14 @@ module ActiveRecord
64
69
  foreign_key = reflection.active_record_primary_key
65
70
  end
66
71
 
67
- conditions = self.conditions[i]
68
-
69
72
  if reflection == chain.last
70
- scope = scope.where(table[key].eq(owner[foreign_key]))
73
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
74
+ scope = scope.where(table[key].eq(bind_val))
71
75
 
72
76
  if reflection.type
73
- scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
74
- end
75
-
76
- conditions.each do |condition|
77
- if options[:through] && condition.is_a?(Hash)
78
- condition = disambiguate_condition(table, condition)
79
- end
80
-
81
- scope = scope.where(interpolate(condition))
77
+ value = owner.class.base_class.name
78
+ bind_val = bind scope, table.table_name, reflection.type.to_s, value
79
+ scope = scope.where(table[reflection.type].eq(bind_val))
82
80
  end
83
81
  else
84
82
  constraint = table[key].eq(foreign_table[foreign_key])
@@ -89,10 +87,26 @@ module ActiveRecord
89
87
  end
90
88
 
91
89
  scope = scope.joins(join(foreign_table, constraint))
90
+ end
91
+
92
+ is_first_chain = i == 0
93
+ klass = is_first_chain ? self.klass : reflection.klass
92
94
 
93
- unless conditions.empty?
94
- scope = scope.where(sanitize(conditions, table))
95
+ # Exclude the scope of the association itself, because that
96
+ # was already merged in the #scope method.
97
+ scope_chain[i].each do |scope_chain_item|
98
+ item = eval_scope(klass, scope_chain_item)
99
+
100
+ if scope_chain_item == self.reflection.scope
101
+ scope.merge! item.except(:where, :includes)
102
+ end
103
+
104
+ if is_first_chain
105
+ scope.includes! item.includes_values
95
106
  end
107
+
108
+ scope.where_values += item.where_values
109
+ scope.order_values |= item.order_values
96
110
  end
97
111
  end
98
112
 
@@ -114,19 +128,11 @@ module ActiveRecord
114
128
  end
115
129
  end
116
130
 
117
- def disambiguate_condition(table, condition)
118
- if condition.is_a?(Hash)
119
- Hash[
120
- condition.map do |k, v|
121
- if v.is_a?(Hash)
122
- [k, v]
123
- else
124
- [table.table_alias || table.name, { k => v }]
125
- end
126
- end
127
- ]
131
+ def eval_scope(klass, scope)
132
+ if scope.is_a?(Relation)
133
+ scope
128
134
  else
129
- condition
135
+ klass.unscoped.instance_exec(owner, &scope)
130
136
  end
131
137
  end
132
138
  end
@@ -1,9 +1,14 @@
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
+
6
+ def handle_dependency
7
+ target.send(options[:dependent]) if load_target
8
+ end
9
+
5
10
  def replace(record)
6
- raise_on_type_mismatch(record) if record
11
+ raise_on_type_mismatch!(record) if record
7
12
 
8
13
  update_counters(record)
9
14
  replace_keys(record)
@@ -14,6 +19,11 @@ module ActiveRecord
14
19
  self.target = record
15
20
  end
16
21
 
22
+ def reset
23
+ super
24
+ @updated = false
25
+ end
26
+
17
27
  def updated?
18
28
  @updated
19
29
  end
@@ -40,8 +50,11 @@ module ActiveRecord
40
50
 
41
51
  # Checks whether record is different to the current target, without loading it
42
52
  def different_target?(record)
43
- record.nil? && owner[reflection.foreign_key] ||
44
- record && record.id != owner[reflection.foreign_key]
53
+ if record.nil?
54
+ owner[reflection.foreign_key]
55
+ else
56
+ record.id != owner[reflection.foreign_key]
57
+ end
45
58
  end
46
59
 
47
60
  def 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
 
@@ -1,55 +1,108 @@
1
1
  module ActiveRecord::Associations::Builder
2
2
  class Association #:nodoc:
3
- class_attribute :valid_options
4
- self.valid_options = [:class_name, :foreign_key, :select, :conditions, :include, :extend, :readonly, :validate]
3
+ class << self
4
+ attr_accessor :valid_options
5
+ end
5
6
 
6
- # Set by subclasses
7
- class_attribute :macro
7
+ self.valid_options = [:class_name, :foreign_key, :validate]
8
8
 
9
- attr_reader :model, :name, :options, :reflection
9
+ attr_reader :model, :name, :scope, :options, :reflection
10
10
 
11
- def self.build(model, name, options)
12
- new(model, name, options).build
11
+ def self.build(*args, &block)
12
+ new(*args, &block).build
13
13
  end
14
14
 
15
- def initialize(model, name, options)
16
- @model, @name, @options = model, name, options
15
+ def initialize(model, name, scope, options)
16
+ raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
17
+
18
+ @model = model
19
+ @name = name
20
+
21
+ if scope.is_a?(Hash)
22
+ @scope = nil
23
+ @options = scope
24
+ else
25
+ @scope = scope
26
+ @options = options
27
+ end
28
+
29
+ if @scope && @scope.arity == 0
30
+ prev_scope = @scope
31
+ @scope = proc { instance_exec(&prev_scope) }
32
+ end
17
33
  end
18
34
 
19
35
  def mixin
20
36
  @model.generated_feature_methods
21
37
  end
22
38
 
39
+ include Module.new { def build; end }
40
+
23
41
  def build
24
42
  validate_options
25
- reflection = model.create_reflection(self.class.macro, name, options, model)
26
43
  define_accessors
27
- reflection
44
+ configure_dependency if options[:dependent]
45
+ @reflection = model.create_reflection(macro, name, scope, options, model)
46
+ super # provides an extension point
47
+ @reflection
28
48
  end
29
49
 
30
- private
50
+ def macro
51
+ raise NotImplementedError
52
+ end
31
53
 
32
- def validate_options
33
- options.assert_valid_keys(self.class.valid_options)
34
- end
54
+ def valid_options
55
+ Association.valid_options
56
+ end
35
57
 
36
- def define_accessors
37
- define_readers
38
- define_writers
39
- end
58
+ def validate_options
59
+ options.assert_valid_keys(valid_options)
60
+ end
61
+
62
+ def define_accessors
63
+ define_readers
64
+ define_writers
65
+ end
40
66
 
41
- def define_readers
42
- name = self.name
43
- mixin.redefine_method(name) do |*params|
44
- association(name).reader(*params)
67
+ def define_readers
68
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
69
+ def #{name}(*args)
70
+ association(:#{name}).reader(*args)
45
71
  end
46
- end
72
+ CODE
73
+ end
47
74
 
48
- def define_writers
49
- name = self.name
50
- mixin.redefine_method("#{name}=") do |value|
51
- association(name).writer(value)
75
+ def define_writers
76
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
77
+ def #{name}=(value)
78
+ association(:#{name}).writer(value)
52
79
  end
80
+ CODE
81
+ end
82
+
83
+ def configure_dependency
84
+ unless valid_dependent_options.include? options[:dependent]
85
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}"
86
+ end
87
+
88
+ if options[:dependent] == :restrict
89
+ ActiveSupport::Deprecation.warn(
90
+ "The :restrict option is deprecated. Please use :restrict_with_exception instead, which " \
91
+ "provides the same functionality."
92
+ )
53
93
  end
94
+
95
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
96
+ def #{macro}_dependent_for_#{name}
97
+ association(:#{name}).handle_dependency
98
+ end
99
+ CODE
100
+
101
+ model.before_destroy "#{macro}_dependent_for_#{name}"
102
+ end
103
+
104
+ def valid_dependent_options
105
+ raise NotImplementedError
106
+ end
54
107
  end
55
108
  end
@@ -1,10 +1,12 @@
1
- require 'active_support/core_ext/object/inclusion'
2
-
3
1
  module ActiveRecord::Associations::Builder
4
2
  class BelongsTo < SingularAssociation #:nodoc:
5
- self.macro = :belongs_to
3
+ def macro
4
+ :belongs_to
5
+ end
6
6
 
7
- self.valid_options += [:foreign_type, :polymorphic, :touch]
7
+ def valid_options
8
+ super + [:foreign_type, :polymorphic, :touch, :counter_cache]
9
+ end
8
10
 
9
11
  def constructable?
10
12
  !options[:polymorphic]
@@ -14,75 +16,90 @@ module ActiveRecord::Associations::Builder
14
16
  reflection = super
15
17
  add_counter_cache_callbacks(reflection) if options[:counter_cache]
16
18
  add_touch_callbacks(reflection) if options[:touch]
17
- configure_dependency
18
19
  reflection
19
20
  end
20
21
 
21
- private
22
+ def add_counter_cache_callbacks(reflection)
23
+ cache_column = reflection.counter_cache_column
24
+ foreign_key = reflection.foreign_key
25
+ klass = reflection.class_name.safe_constantize
22
26
 
23
- def add_counter_cache_callbacks(reflection)
24
- cache_column = reflection.counter_cache_column
25
- name = self.name
26
-
27
- method_name = "belongs_to_counter_cache_after_create_for_#{name}"
28
- mixin.redefine_method(method_name) do
29
- record = send(name)
30
- record.class.increment_counter(cache_column, record.id) unless record.nil?
27
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
28
+ def belongs_to_counter_cache_after_create_for_#{name}
29
+ if record = #{name}
30
+ record.class.increment_counter(:#{cache_column}, record.id)
31
+ @_after_create_counter_called = true
32
+ end
31
33
  end
32
- model.after_create(method_name)
33
-
34
- method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
35
- mixin.redefine_method(method_name) do
36
- record = send(name)
37
34
 
38
- if record && !self.destroyed?
39
- record.class.decrement_counter(cache_column, record.id)
35
+ def belongs_to_counter_cache_before_destroy_for_#{name}
36
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
37
+ record = #{name}
38
+ if record && !self.destroyed?
39
+ record.class.decrement_counter(:#{cache_column}, record.id)
40
+ end
40
41
  end
41
42
  end
42
- model.before_destroy(method_name)
43
-
44
- model.send(:module_eval,
45
- "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)", __FILE__, __LINE__
46
- )
47
- end
48
-
49
- def add_touch_callbacks(reflection)
50
- name = self.name
51
- method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
52
- touch = options[:touch]
53
43
 
54
- mixin.redefine_method(method_name) do
55
- record = send(name)
44
+ def belongs_to_counter_cache_after_update_for_#{name}
45
+ if (@_after_create_counter_called ||= false)
46
+ @_after_create_counter_called = false
47
+ elsif self.#{foreign_key}_changed? && !new_record? && #{constructable?}
48
+ model = #{klass}
49
+ foreign_key_was = self.#{foreign_key}_was
50
+ foreign_key = self.#{foreign_key}
56
51
 
57
- unless record.nil?
58
- if touch == true
59
- record.touch
60
- else
61
- record.touch(touch)
52
+ if foreign_key && model.respond_to?(:increment_counter)
53
+ model.increment_counter(:#{cache_column}, foreign_key)
54
+ end
55
+ if foreign_key_was && model.respond_to?(:decrement_counter)
56
+ model.decrement_counter(:#{cache_column}, foreign_key_was)
62
57
  end
63
58
  end
64
59
  end
60
+ CODE
65
61
 
66
- model.after_save(method_name)
67
- model.after_touch(method_name)
68
- model.after_destroy(method_name)
69
- end
62
+ model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
63
+ model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
64
+ model.after_update "belongs_to_counter_cache_after_update_for_#{name}"
65
+ klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
66
+ end
70
67
 
71
- def configure_dependency
72
- if options[:dependent]
73
- unless options[:dependent].in?([:destroy, :delete])
74
- raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{options[:dependent].inspect})"
75
- end
68
+ def add_touch_callbacks(reflection)
69
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
70
+ def belongs_to_touch_after_save_or_destroy_for_#{name}
71
+ foreign_key_field = #{reflection.foreign_key.inspect}
72
+ old_foreign_id = changed_attributes[foreign_key_field]
73
+
74
+ if old_foreign_id
75
+ association = association(:#{name})
76
+ reflection = association.reflection
77
+ if reflection.polymorphic?
78
+ klass = send("#{reflection.foreign_type}_was").constantize
79
+ else
80
+ klass = association.klass
81
+ end
82
+ old_record = klass.find_by(klass.primary_key => old_foreign_id)
76
83
 
77
- method_name = "belongs_to_dependent_#{options[:dependent]}_for_#{name}"
78
- model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
79
- def #{method_name}
80
- association = #{name}
81
- association.#{options[:dependent]} if association
84
+ if old_record
85
+ old_record.touch #{options[:touch].inspect if options[:touch] != true}
82
86
  end
83
- eoruby
84
- model.after_destroy method_name
87
+ end
88
+
89
+ record = #{name}
90
+ if record && record.persisted?
91
+ record.touch #{options[:touch].inspect if options[:touch] != true}
92
+ end
85
93
  end
86
- end
94
+ CODE
95
+
96
+ model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}"
97
+ model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}"
98
+ model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}"
99
+ end
100
+
101
+ def valid_dependent_options
102
+ [:destroy, :delete]
103
+ end
87
104
  end
88
105
  end
@@ -1,24 +1,24 @@
1
+ require 'active_record/associations'
2
+
1
3
  module ActiveRecord::Associations::Builder
2
4
  class CollectionAssociation < Association #:nodoc:
3
- CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
4
-
5
- self.valid_options += [
6
- :table_name, :order, :group, :having, :limit, :offset, :uniq, :finder_sql,
7
- :counter_sql, :before_add, :after_add, :before_remove, :after_remove
8
- ]
9
5
 
10
- attr_reader :block_extension
6
+ CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
11
7
 
12
- def self.build(model, name, options, &extension)
13
- new(model, name, options, &extension).build
8
+ def valid_options
9
+ super + [:table_name, :finder_sql, :counter_sql, :before_add,
10
+ :after_add, :before_remove, :after_remove, :extend]
14
11
  end
15
12
 
16
- def initialize(model, name, options, &extension)
17
- super(model, name, options)
13
+ attr_reader :block_extension, :extension_module
14
+
15
+ def initialize(*args, &extension)
16
+ super(*args)
18
17
  @block_extension = extension
19
18
  end
20
19
 
21
20
  def build
21
+ show_deprecation_warnings
22
22
  wrap_block_extension
23
23
  reflection = super
24
24
  CALLBACKS.each { |callback_name| define_callback(callback_name) }
@@ -29,47 +29,61 @@ module ActiveRecord::Associations::Builder
29
29
  true
30
30
  end
31
31
 
32
- private
32
+ def show_deprecation_warnings
33
+ [:finder_sql, :counter_sql].each do |name|
34
+ if options.include? name
35
+ ActiveSupport::Deprecation.warn("The :#{name} association option is deprecated. Please find an alternative (such as using scopes).")
36
+ end
37
+ end
38
+ end
39
+
40
+ def wrap_block_extension
41
+ if block_extension
42
+ @extension_module = mod = Module.new(&block_extension)
43
+ silence_warnings do
44
+ model.parent.const_set(extension_module_name, mod)
45
+ end
33
46
 
34
- def wrap_block_extension
35
- options[:extend] = Array.wrap(options[:extend])
47
+ prev_scope = @scope
36
48
 
37
- if block_extension
38
- silence_warnings do
39
- model.parent.const_set(extension_module_name, Module.new(&block_extension))
40
- end
41
- options[:extend].push("#{model.parent}::#{extension_module_name}".constantize)
49
+ if prev_scope
50
+ @scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) }
51
+ else
52
+ @scope = proc { extending(mod) }
42
53
  end
43
54
  end
55
+ end
44
56
 
45
- def extension_module_name
46
- @extension_module_name ||= "#{model.to_s.demodulize}#{name.to_s.camelize}AssociationExtension"
47
- end
57
+ def extension_module_name
58
+ @extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
59
+ end
48
60
 
49
- def define_callback(callback_name)
50
- full_callback_name = "#{callback_name}_for_#{name}"
61
+ def define_callback(callback_name)
62
+ full_callback_name = "#{callback_name}_for_#{name}"
51
63
 
52
- # TODO : why do i need method_defined? I think its because of the inheritance chain
53
- model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
54
- model.send("#{full_callback_name}=", Array.wrap(options[callback_name.to_sym]))
55
- end
64
+ # TODO : why do i need method_defined? I think its because of the inheritance chain
65
+ model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
66
+ model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
67
+ end
56
68
 
57
- def define_readers
58
- super
69
+ def define_readers
70
+ super
59
71
 
60
- name = self.name
61
- mixin.redefine_method("#{name.to_s.singularize}_ids") do
62
- association(name).ids_reader
72
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
73
+ def #{name.to_s.singularize}_ids
74
+ association(:#{name}).ids_reader
63
75
  end
64
- end
76
+ CODE
77
+ end
65
78
 
66
- def define_writers
67
- super
79
+ def define_writers
80
+ super
68
81
 
69
- name = self.name
70
- mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
71
- association(name).ids_writer(ids)
82
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
83
+ def #{name.to_s.singularize}_ids=(ids)
84
+ association(:#{name}).ids_writer(ids)
72
85
  end
73
- end
86
+ CODE
87
+ end
74
88
  end
75
89
  end