activerecord 4.0.13 → 4.1.0.beta1

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +745 -2700
  3. data/README.rdoc +2 -2
  4. data/examples/performance.rb +30 -18
  5. data/examples/simple.rb +4 -4
  6. data/lib/active_record.rb +2 -6
  7. data/lib/active_record/aggregations.rb +2 -1
  8. data/lib/active_record/association_relation.rb +0 -4
  9. data/lib/active_record/associations.rb +87 -43
  10. data/lib/active_record/associations/alias_tracker.rb +1 -3
  11. data/lib/active_record/associations/association.rb +8 -16
  12. data/lib/active_record/associations/association_scope.rb +5 -16
  13. data/lib/active_record/associations/belongs_to_association.rb +34 -25
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  15. data/lib/active_record/associations/builder/association.rb +78 -54
  16. data/lib/active_record/associations/builder/belongs_to.rb +91 -58
  17. data/lib/active_record/associations/builder/collection_association.rb +47 -45
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -25
  19. data/lib/active_record/associations/builder/has_many.rb +2 -2
  20. data/lib/active_record/associations/builder/has_one.rb +5 -7
  21. data/lib/active_record/associations/builder/singular_association.rb +6 -7
  22. data/lib/active_record/associations/collection_association.rb +68 -105
  23. data/lib/active_record/associations/collection_proxy.rb +12 -15
  24. data/lib/active_record/associations/has_many_association.rb +11 -9
  25. data/lib/active_record/associations/has_many_through_association.rb +16 -12
  26. data/lib/active_record/associations/has_one_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +204 -165
  28. data/lib/active_record/associations/join_dependency/join_association.rb +43 -101
  29. data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
  30. data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
  31. data/lib/active_record/associations/join_helper.rb +2 -11
  32. data/lib/active_record/associations/preloader.rb +89 -34
  33. data/lib/active_record/associations/preloader/association.rb +43 -25
  34. data/lib/active_record/associations/preloader/collection_association.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +58 -26
  38. data/lib/active_record/associations/singular_association.rb +6 -5
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +5 -2
  41. data/lib/active_record/attribute_methods.rb +45 -40
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
  43. data/lib/active_record/attribute_methods/dirty.rb +8 -22
  44. data/lib/active_record/attribute_methods/primary_key.rb +1 -7
  45. data/lib/active_record/attribute_methods/read.rb +55 -28
  46. data/lib/active_record/attribute_methods/serialization.rb +12 -33
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -13
  48. data/lib/active_record/attribute_methods/write.rb +37 -12
  49. data/lib/active_record/autosave_association.rb +207 -207
  50. data/lib/active_record/base.rb +5 -1
  51. data/lib/active_record/callbacks.rb +2 -2
  52. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -7
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +11 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -14
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -5
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +84 -0
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +52 -83
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +0 -5
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +14 -97
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +58 -60
  63. data/lib/active_record/connection_adapters/column.rb +1 -35
  64. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +3 -4
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +16 -15
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +24 -18
  68. data/lib/active_record/connection_adapters/postgresql/cast.rb +20 -16
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +23 -43
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +19 -12
  71. data/lib/active_record/connection_adapters/postgresql/quoting.rb +28 -23
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -30
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +92 -75
  74. data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +31 -64
  76. data/lib/active_record/connection_handling.rb +2 -2
  77. data/lib/active_record/core.rb +22 -43
  78. data/lib/active_record/counter_cache.rb +7 -7
  79. data/lib/active_record/enum.rb +100 -0
  80. data/lib/active_record/errors.rb +10 -5
  81. data/lib/active_record/fixture_set/file.rb +2 -1
  82. data/lib/active_record/fixtures.rb +171 -74
  83. data/lib/active_record/inheritance.rb +16 -22
  84. data/lib/active_record/integration.rb +52 -1
  85. data/lib/active_record/locking/optimistic.rb +7 -2
  86. data/lib/active_record/locking/pessimistic.rb +1 -1
  87. data/lib/active_record/log_subscriber.rb +5 -12
  88. data/lib/active_record/migration.rb +62 -46
  89. data/lib/active_record/migration/command_recorder.rb +7 -13
  90. data/lib/active_record/model_schema.rb +7 -14
  91. data/lib/active_record/nested_attributes.rb +10 -8
  92. data/lib/active_record/no_touching.rb +52 -0
  93. data/lib/active_record/null_relation.rb +3 -3
  94. data/lib/active_record/persistence.rb +16 -34
  95. data/lib/active_record/querying.rb +14 -12
  96. data/lib/active_record/railtie.rb +0 -50
  97. data/lib/active_record/railties/databases.rake +12 -15
  98. data/lib/active_record/readonly_attributes.rb +0 -6
  99. data/lib/active_record/reflection.rb +189 -75
  100. data/lib/active_record/relation.rb +69 -94
  101. data/lib/active_record/relation/batches.rb +57 -23
  102. data/lib/active_record/relation/calculations.rb +36 -43
  103. data/lib/active_record/relation/delegation.rb +54 -39
  104. data/lib/active_record/relation/finder_methods.rb +107 -62
  105. data/lib/active_record/relation/merger.rb +7 -20
  106. data/lib/active_record/relation/predicate_builder.rb +57 -38
  107. data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
  108. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  109. data/lib/active_record/relation/query_methods.rb +110 -98
  110. data/lib/active_record/relation/spawn_methods.rb +1 -2
  111. data/lib/active_record/result.rb +45 -6
  112. data/lib/active_record/runtime_registry.rb +5 -0
  113. data/lib/active_record/sanitization.rb +6 -8
  114. data/lib/active_record/schema_dumper.rb +16 -5
  115. data/lib/active_record/schema_migration.rb +24 -25
  116. data/lib/active_record/scoping/default.rb +5 -18
  117. data/lib/active_record/scoping/named.rb +8 -29
  118. data/lib/active_record/store.rb +56 -28
  119. data/lib/active_record/tasks/database_tasks.rb +8 -4
  120. data/lib/active_record/timestamp.rb +4 -4
  121. data/lib/active_record/transactions.rb +8 -10
  122. data/lib/active_record/validations/presence.rb +1 -1
  123. data/lib/active_record/validations/uniqueness.rb +1 -6
  124. data/lib/active_record/version.rb +1 -1
  125. data/lib/rails/generators/active_record.rb +2 -8
  126. data/lib/rails/generators/active_record/migration.rb +18 -0
  127. data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
  128. data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
  129. metadata +32 -45
  130. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
  131. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  132. data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
  133. data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
  134. data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
  135. data/lib/active_record/test_case.rb +0 -102
@@ -60,10 +60,8 @@ module ActiveRecord
60
60
  join.left.downcase.scan(
61
61
  /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
62
62
  ).size
63
- elsif join.respond_to? :left
64
- join.left.table_name == name ? 1 : 0
65
63
  else
66
- 0
64
+ join.left.table_name == name ? 1 : 0
67
65
  end
68
66
  end
69
67
 
@@ -13,7 +13,6 @@ module ActiveRecord
13
13
  # BelongsToAssociation
14
14
  # BelongsToPolymorphicAssociation
15
15
  # CollectionAssociation
16
- # HasAndBelongsToManyAssociation
17
16
  # HasManyAssociation
18
17
  # HasManyThroughAssociation + ThroughAssociation
19
18
  class Association #:nodoc:
@@ -87,11 +86,6 @@ module ActiveRecord
87
86
  target_scope.merge(association_scope)
88
87
  end
89
88
 
90
- def scoped
91
- ActiveSupport::Deprecation.warn "#scoped is deprecated. use #scope instead."
92
- scope
93
- end
94
-
95
89
  # The scope for this association.
96
90
  #
97
91
  # Note that the association_scope is merged into the target_scope only when the
@@ -110,11 +104,12 @@ module ActiveRecord
110
104
 
111
105
  # Set the inverse association, if possible
112
106
  def set_inverse_instance(record)
113
- if record && invertible_for?(record)
107
+ if invertible_for?(record)
114
108
  inverse = record.association(inverse_reflection_for(record).name)
115
109
  inverse.target = owner
116
110
  inverse.inversed = true
117
111
  end
112
+ record
118
113
  end
119
114
 
120
115
  # Returns the class of the target. belongs_to polymorphic overrides this to look at the
@@ -126,11 +121,7 @@ module ActiveRecord
126
121
  # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
127
122
  # through association's scope)
128
123
  def target_scope
129
- all = klass.all
130
- scope = AssociationRelation.new(klass, klass.arel_table, self)
131
- scope.merge! all
132
- scope.default_scoped = all.default_scoped?
133
- scope
124
+ AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all)
134
125
  end
135
126
 
136
127
  # Loads the \target if needed and returns it.
@@ -204,13 +195,14 @@ module ActiveRecord
204
195
  creation_attributes.each { |key, value| record[key] = value }
205
196
  end
206
197
 
207
- # Should be true if there is a foreign key present on the owner which
198
+ # Returns true if there is a foreign key present on the owner which
208
199
  # references the target. This is used to determine whether we can load
209
200
  # the target if the owner is currently a new record (and therefore
210
- # without a key).
201
+ # without a key). If the owner is a new record then foreign_key must
202
+ # be present in order to load target.
211
203
  #
212
204
  # Currently implemented by belongs_to (vanilla and polymorphic) and
213
- # has_one/has_many :through associations which go through a belongs_to
205
+ # has_one/has_many :through associations which go through a belongs_to.
214
206
  def foreign_key_present?
215
207
  false
216
208
  end
@@ -240,7 +232,7 @@ module ActiveRecord
240
232
 
241
233
  # Returns true if record contains the foreign_key
242
234
  def foreign_key_for?(record)
243
- record.has_attribute?(reflection.foreign_key)
235
+ record.attributes.has_key? reflection.foreign_key
244
236
  end
245
237
 
246
238
  # This should be implemented to return the values of the relevant key(s) on the owner,
@@ -44,18 +44,6 @@ module ActiveRecord
44
44
  chain.each_with_index do |reflection, i|
45
45
  table, foreign_table = tables.shift, tables.first
46
46
 
47
- if reflection.source_macro == :has_and_belongs_to_many
48
- join_table = tables.shift
49
-
50
- scope = scope.joins(join(
51
- join_table,
52
- table[reflection.association_primary_key].
53
- eq(join_table[reflection.association_foreign_key])
54
- ))
55
-
56
- table, foreign_table = join_table, tables.first
57
- end
58
-
59
47
  if reflection.source_macro == :belongs_to
60
48
  if reflection.options[:polymorphic]
61
49
  key = reflection.association_primary_key(self.klass)
@@ -82,8 +70,9 @@ module ActiveRecord
82
70
  constraint = table[key].eq(foreign_table[foreign_key])
83
71
 
84
72
  if reflection.type
85
- type = chain[i + 1].klass.base_class.name
86
- constraint = constraint.and(table[reflection.type].eq(type))
73
+ value = chain[i + 1].klass.base_class.name
74
+ bind_val = bind scope, table.table_name, reflection.type.to_s, value
75
+ scope = scope.where(table[reflection.type].eq(bind_val))
87
76
  end
88
77
 
89
78
  scope = scope.joins(join(foreign_table, constraint))
@@ -98,7 +87,7 @@ module ActiveRecord
98
87
  item = eval_scope(klass, scope_chain_item)
99
88
 
100
89
  if scope_chain_item == self.reflection.scope
101
- scope.merge! item.except(:where, :includes)
90
+ scope.merge! item.except(:where, :includes, :bind)
102
91
  end
103
92
 
104
93
  if is_first_chain
@@ -124,7 +113,7 @@ module ActiveRecord
124
113
  # the owner
125
114
  klass.table_name
126
115
  else
127
- reflection.table_name
116
+ super
128
117
  end
129
118
  end
130
119
 
@@ -8,13 +8,16 @@ module ActiveRecord
8
8
  end
9
9
 
10
10
  def replace(record)
11
- raise_on_type_mismatch!(record) if record
12
-
13
- update_counters(record)
14
- replace_keys(record)
15
- set_inverse_instance(record)
16
-
17
- @updated = true if record
11
+ if record
12
+ raise_on_type_mismatch!(record)
13
+ update_counters(record)
14
+ replace_keys(record)
15
+ set_inverse_instance(record)
16
+ @updated = true
17
+ else
18
+ decrement_counters
19
+ remove_keys
20
+ end
18
21
 
19
22
  self.target = record
20
23
  end
@@ -34,35 +37,41 @@ module ActiveRecord
34
37
  !loaded? && foreign_key_present? && klass
35
38
  end
36
39
 
37
- def update_counters(record)
40
+ def with_cache_name
38
41
  counter_cache_name = reflection.counter_cache_column
42
+ return unless counter_cache_name && owner.persisted?
43
+ yield counter_cache_name
44
+ end
45
+
46
+ def update_counters(record)
47
+ with_cache_name do |name|
48
+ return unless different_target? record
49
+ record.class.increment_counter(name, record.id)
50
+ decrement_counter name
51
+ end
52
+ end
39
53
 
40
- if counter_cache_name && owner.persisted? && different_target?(record)
41
- if record
42
- record.class.increment_counter(counter_cache_name, record.id)
43
- end
54
+ def decrement_counters
55
+ with_cache_name { |name| decrement_counter name }
56
+ end
44
57
 
45
- if foreign_key_present?
46
- klass.decrement_counter(counter_cache_name, target_id)
47
- end
58
+ def decrement_counter counter_cache_name
59
+ if foreign_key_present?
60
+ klass.decrement_counter(counter_cache_name, target_id)
48
61
  end
49
62
  end
50
63
 
51
64
  # Checks whether record is different to the current target, without loading it
52
65
  def different_target?(record)
53
- if record.nil?
54
- owner[reflection.foreign_key]
55
- else
56
- record.id != owner[reflection.foreign_key]
57
- end
66
+ record.id != owner[reflection.foreign_key]
58
67
  end
59
68
 
60
69
  def replace_keys(record)
61
- if record
62
- owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
63
- else
64
- owner[reflection.foreign_key] = nil
65
- end
70
+ owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
71
+ end
72
+
73
+ def remove_keys
74
+ owner[reflection.foreign_key] = nil
66
75
  end
67
76
 
68
77
  def foreign_key_present?
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
 
12
12
  def replace_keys(record)
13
13
  super
14
- owner[reflection.foreign_type] = record && record.class.base_class.name
14
+ owner[reflection.foreign_type] = record.class.base_class.name
15
15
  end
16
16
 
17
17
  def different_target?(record)
@@ -1,50 +1,66 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+
3
+ # This is the parent Association class which defines the variables
4
+ # used by all associations.
5
+ #
6
+ # The hierarchy is defined as follows:
7
+ # Association
8
+ # - SingularAssociation
9
+ # - BelongsToAssociation
10
+ # - HasOneAssociation
11
+ # - CollectionAssociation
12
+ # - HasManyAssociation
13
+
1
14
  module ActiveRecord::Associations::Builder
2
15
  class Association #:nodoc:
3
16
  class << self
17
+ attr_accessor :extensions
18
+ # TODO: This class accessor is needed to make activerecord-deprecated_finders work.
19
+ # We can move it to a constant in 5.0.
4
20
  attr_accessor :valid_options
5
21
  end
22
+ self.extensions = []
6
23
 
7
- self.valid_options = [:class_name, :foreign_key, :validate]
24
+ self.valid_options = [:class_name, :class, :foreign_key, :validate]
8
25
 
9
- attr_reader :model, :name, :scope, :options, :reflection
26
+ attr_reader :name, :scope, :options
10
27
 
11
- def self.build(*args, &block)
12
- new(*args, &block).build
28
+ def self.build(model, name, scope, options, &block)
29
+ builder = create_builder model, name, scope, options, &block
30
+ reflection = builder.build(model)
31
+ define_accessors model, reflection
32
+ define_callbacks model, reflection
33
+ builder.define_extensions model
34
+ reflection
13
35
  end
14
36
 
15
- def initialize(model, name, scope, options)
37
+ def self.create_builder(model, name, scope, options, &block)
16
38
  raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
17
39
 
18
- @model = model
19
- @name = name
40
+ new(model, name, scope, options, &block)
41
+ end
20
42
 
43
+ def initialize(model, name, scope, options)
44
+ # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
21
45
  if scope.is_a?(Hash)
22
- @scope = nil
23
- @options = scope
24
- else
25
- @scope = scope
26
- @options = options
46
+ options = scope
47
+ scope = nil
27
48
  end
28
49
 
29
- if @scope && @scope.arity == 0
30
- prev_scope = @scope
31
- @scope = proc { instance_exec(&prev_scope) }
32
- end
33
- end
50
+ # TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
51
+ @name = name
52
+ @scope = scope
53
+ @options = options
34
54
 
35
- def mixin
36
- @model.generated_feature_methods
37
- end
55
+ validate_options
38
56
 
39
- include Module.new { def build; end }
57
+ if scope && scope.arity == 0
58
+ @scope = proc { instance_exec(&scope) }
59
+ end
60
+ end
40
61
 
41
- def build
42
- validate_options
43
- define_accessors
44
- configure_dependency if options[:dependent]
45
- @reflection = model.create_reflection(macro, name, scope, options, model)
46
- super # provides an extension point
47
- @reflection
62
+ def build(model)
63
+ ActiveRecord::Reflection.create(macro, name, scope, options, model)
48
64
  end
49
65
 
50
66
  def macro
@@ -52,19 +68,37 @@ module ActiveRecord::Associations::Builder
52
68
  end
53
69
 
54
70
  def valid_options
55
- Association.valid_options
71
+ Association.valid_options + Association.extensions.flat_map(&:valid_options)
56
72
  end
57
73
 
58
74
  def validate_options
59
75
  options.assert_valid_keys(valid_options)
60
76
  end
61
77
 
62
- def define_accessors
63
- define_readers
64
- define_writers
78
+ def define_extensions(model)
65
79
  end
66
80
 
67
- def define_readers
81
+ def self.define_callbacks(model, reflection)
82
+ add_before_destroy_callbacks(model, reflection) if reflection.options[:dependent]
83
+ Association.extensions.each do |extension|
84
+ extension.build model, reflection
85
+ end
86
+ end
87
+
88
+ # Defines the setter and getter methods for the association
89
+ # class Post < ActiveRecord::Base
90
+ # has_many :comments
91
+ # end
92
+ #
93
+ # Post.first.comments and Post.first.comments= methods are defined by this method...
94
+ def self.define_accessors(model, reflection)
95
+ mixin = model.generated_association_methods
96
+ name = reflection.name
97
+ define_readers(mixin, name)
98
+ define_writers(mixin, name)
99
+ end
100
+
101
+ def self.define_readers(mixin, name)
68
102
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
69
103
  def #{name}(*args)
70
104
  association(:#{name}).reader(*args)
@@ -72,7 +106,7 @@ module ActiveRecord::Associations::Builder
72
106
  CODE
73
107
  end
74
108
 
75
- def define_writers
109
+ def self.define_writers(mixin, name)
76
110
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
77
111
  def #{name}=(value)
78
112
  association(:#{name}).writer(value)
@@ -80,29 +114,19 @@ module ActiveRecord::Associations::Builder
80
114
  CODE
81
115
  end
82
116
 
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
- )
93
- end
117
+ def self.valid_dependent_options
118
+ raise NotImplementedError
119
+ end
94
120
 
95
- mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
96
- def #{macro}_dependent_for_#{name}
97
- association(:#{name}).handle_dependency
98
- end
99
- CODE
121
+ private
100
122
 
101
- model.before_destroy "#{macro}_dependent_for_#{name}"
102
- end
123
+ def self.add_before_destroy_callbacks(model, reflection)
124
+ unless valid_dependent_options.include? reflection.options[:dependent]
125
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{reflection.options[:dependent]}"
126
+ end
103
127
 
104
- def valid_dependent_options
105
- raise NotImplementedError
128
+ name = reflection.name
129
+ model.before_destroy lambda { |o| o.association(name).handle_dependency }
106
130
  end
107
131
  end
108
132
  end
@@ -8,98 +8,131 @@ module ActiveRecord::Associations::Builder
8
8
  super + [:foreign_type, :polymorphic, :touch, :counter_cache]
9
9
  end
10
10
 
11
- def constructable?
12
- !options[:polymorphic]
11
+ def self.valid_dependent_options
12
+ [:destroy, :delete]
13
13
  end
14
14
 
15
- def build
16
- reflection = super
17
- add_counter_cache_callbacks(reflection) if options[:counter_cache]
18
- add_touch_callbacks(reflection) if options[:touch]
19
- reflection
15
+ def self.define_callbacks(model, reflection)
16
+ super
17
+ add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
18
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
20
19
  end
21
20
 
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
21
+ def self.define_accessors(mixin, reflection)
22
+ super
23
+ add_counter_cache_methods mixin
24
+ end
25
+
26
+ private
26
27
 
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)
28
+ def self.add_counter_cache_methods(mixin)
29
+ return if mixin.method_defined? :belongs_to_counter_cache_after_create
30
+
31
+ mixin.class_eval do
32
+ def belongs_to_counter_cache_after_create(reflection)
33
+ if record = send(reflection.name)
34
+ cache_column = reflection.counter_cache_column
35
+ record.class.increment_counter(cache_column, record.id)
31
36
  @_after_create_counter_called = true
32
37
  end
33
38
  end
34
39
 
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}
40
+ def belongs_to_counter_cache_before_destroy(reflection)
41
+ foreign_key = reflection.foreign_key.to_sym
42
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
43
+ record = send reflection.name
38
44
  if record && !self.destroyed?
39
- record.class.decrement_counter(:#{cache_column}, record.id)
45
+ cache_column = reflection.counter_cache_column
46
+ record.class.decrement_counter(cache_column, record.id)
40
47
  end
41
48
  end
42
49
  end
43
50
 
44
- def belongs_to_counter_cache_after_update_for_#{name}
51
+ def belongs_to_counter_cache_after_update(reflection)
52
+ foreign_key = reflection.foreign_key
53
+ cache_column = reflection.counter_cache_column
54
+
45
55
  if (@_after_create_counter_called ||= false)
46
56
  @_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}
57
+ elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable?
58
+ model = reflection.klass
59
+ foreign_key_was = attribute_was foreign_key
60
+ foreign_key = attribute foreign_key
51
61
 
52
62
  if foreign_key && model.respond_to?(:increment_counter)
53
- model.increment_counter(:#{cache_column}, foreign_key)
63
+ model.increment_counter(cache_column, foreign_key)
54
64
  end
55
65
  if foreign_key_was && model.respond_to?(:decrement_counter)
56
- model.decrement_counter(:#{cache_column}, foreign_key_was)
66
+ model.decrement_counter(cache_column, foreign_key_was)
57
67
  end
58
68
  end
59
69
  end
60
- CODE
70
+ end
71
+ end
72
+
73
+ def self.add_counter_cache_callbacks(model, reflection)
74
+ cache_column = reflection.counter_cache_column
75
+
76
+ model.after_create lambda { |record|
77
+ record.belongs_to_counter_cache_after_create(reflection)
78
+ }
61
79
 
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}"
80
+ model.before_destroy lambda { |record|
81
+ record.belongs_to_counter_cache_before_destroy(reflection)
82
+ }
83
+
84
+ model.after_update lambda { |record|
85
+ record.belongs_to_counter_cache_after_update(reflection)
86
+ }
87
+
88
+ klass = reflection.class_name.safe_constantize
65
89
  klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
66
90
  end
67
91
 
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)
92
+ def self.touch_record(o, foreign_key, name, touch) # :nodoc:
93
+ old_foreign_id = o.changed_attributes[foreign_key]
83
94
 
84
- if old_record
85
- old_record.touch #{options[:touch].inspect if options[:touch] != true}
86
- end
87
- end
95
+ if old_foreign_id
96
+ association = o.association(name)
97
+ reflection = association.reflection
98
+ if reflection.polymorphic?
99
+ klass = o.public_send("#{reflection.foreign_type}_was").constantize
100
+ else
101
+ klass = association.klass
102
+ end
103
+ old_record = klass.find_by(klass.primary_key => old_foreign_id)
88
104
 
89
- record = #{name}
90
- if record && record.persisted?
91
- record.touch #{options[:touch].inspect if options[:touch] != true}
105
+ if old_record
106
+ if touch != true
107
+ old_record.touch touch
108
+ else
109
+ old_record.touch
92
110
  end
93
111
  end
94
- CODE
112
+ end
95
113
 
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}"
114
+ record = o.send name
115
+ unless record.nil? || record.new_record?
116
+ if touch != true
117
+ record.touch touch
118
+ else
119
+ record.touch
120
+ end
121
+ end
99
122
  end
100
123
 
101
- def valid_dependent_options
102
- [:destroy, :delete]
124
+ def self.add_touch_callbacks(model, reflection)
125
+ foreign_key = reflection.foreign_key
126
+ n = reflection.name
127
+ touch = reflection.options[:touch]
128
+
129
+ callback = lambda { |record|
130
+ BelongsTo.touch_record(record, foreign_key, n, touch)
131
+ }
132
+
133
+ model.after_save callback
134
+ model.after_touch callback
135
+ model.after_destroy callback
103
136
  end
104
137
  end
105
138
  end