activerecord 3.0.20 → 3.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 (122) hide show
  1. data/CHANGELOG +220 -91
  2. data/README.rdoc +3 -3
  3. data/examples/performance.rb +88 -109
  4. data/lib/active_record.rb +6 -2
  5. data/lib/active_record/aggregations.rb +22 -45
  6. data/lib/active_record/associations.rb +264 -991
  7. data/lib/active_record/associations/alias_tracker.rb +85 -0
  8. data/lib/active_record/associations/association.rb +231 -0
  9. data/lib/active_record/associations/association_scope.rb +120 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +40 -60
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
  12. data/lib/active_record/associations/builder/association.rb +53 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
  16. data/lib/active_record/associations/builder/has_many.rb +65 -0
  17. data/lib/active_record/associations/builder/has_one.rb +63 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +524 -0
  20. data/lib/active_record/associations/collection_proxy.rb +125 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
  22. data/lib/active_record/associations/has_many_association.rb +50 -79
  23. data/lib/active_record/associations/has_many_through_association.rb +98 -67
  24. data/lib/active_record/associations/has_one_association.rb +45 -115
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency.rb +215 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_helper.rb +56 -0
  31. data/lib/active_record/associations/preloader.rb +177 -0
  32. data/lib/active_record/associations/preloader/association.rb +126 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  42. data/lib/active_record/associations/singular_association.rb +55 -0
  43. data/lib/active_record/associations/through_association.rb +80 -0
  44. data/lib/active_record/attribute_methods.rb +19 -5
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
  46. data/lib/active_record/attribute_methods/dirty.rb +8 -2
  47. data/lib/active_record/attribute_methods/primary_key.rb +33 -13
  48. data/lib/active_record/attribute_methods/read.rb +17 -17
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
  50. data/lib/active_record/attribute_methods/write.rb +2 -1
  51. data/lib/active_record/autosave_association.rb +66 -45
  52. data/lib/active_record/base.rb +445 -273
  53. data/lib/active_record/callbacks.rb +24 -33
  54. data/lib/active_record/coders/yaml_column.rb +41 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
  56. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
  62. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
  64. data/lib/active_record/connection_adapters/column.rb +268 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
  68. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
  69. data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
  70. data/lib/active_record/counter_cache.rb +7 -4
  71. data/lib/active_record/fixtures.rb +174 -192
  72. data/lib/active_record/identity_map.rb +131 -0
  73. data/lib/active_record/locking/optimistic.rb +20 -14
  74. data/lib/active_record/locking/pessimistic.rb +4 -4
  75. data/lib/active_record/log_subscriber.rb +24 -4
  76. data/lib/active_record/migration.rb +265 -144
  77. data/lib/active_record/migration/command_recorder.rb +103 -0
  78. data/lib/active_record/named_scope.rb +68 -25
  79. data/lib/active_record/nested_attributes.rb +58 -15
  80. data/lib/active_record/observer.rb +3 -7
  81. data/lib/active_record/persistence.rb +58 -38
  82. data/lib/active_record/query_cache.rb +25 -3
  83. data/lib/active_record/railtie.rb +21 -12
  84. data/lib/active_record/railties/console_sandbox.rb +6 -0
  85. data/lib/active_record/railties/databases.rake +147 -116
  86. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  87. data/lib/active_record/reflection.rb +176 -44
  88. data/lib/active_record/relation.rb +125 -49
  89. data/lib/active_record/relation/batches.rb +7 -5
  90. data/lib/active_record/relation/calculations.rb +50 -18
  91. data/lib/active_record/relation/finder_methods.rb +47 -26
  92. data/lib/active_record/relation/predicate_builder.rb +24 -21
  93. data/lib/active_record/relation/query_methods.rb +117 -101
  94. data/lib/active_record/relation/spawn_methods.rb +27 -20
  95. data/lib/active_record/result.rb +34 -0
  96. data/lib/active_record/schema.rb +5 -6
  97. data/lib/active_record/schema_dumper.rb +11 -13
  98. data/lib/active_record/serialization.rb +2 -2
  99. data/lib/active_record/serializers/xml_serializer.rb +10 -10
  100. data/lib/active_record/session_store.rb +8 -2
  101. data/lib/active_record/test_case.rb +9 -20
  102. data/lib/active_record/timestamp.rb +21 -9
  103. data/lib/active_record/transactions.rb +16 -15
  104. data/lib/active_record/validations.rb +21 -22
  105. data/lib/active_record/validations/associated.rb +3 -1
  106. data/lib/active_record/validations/uniqueness.rb +48 -58
  107. data/lib/active_record/version.rb +3 -3
  108. data/lib/rails/generators/active_record.rb +6 -0
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
  110. data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
  111. data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
  112. data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
  113. data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
  114. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  115. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
  116. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
  117. metadata +106 -77
  118. checksums.yaml +0 -7
  119. data/lib/active_record/association_preload.rb +0 -431
  120. data/lib/active_record/associations/association_collection.rb +0 -572
  121. data/lib/active_record/associations/association_proxy.rb +0 -304
  122. data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -0,0 +1,85 @@
1
+ require 'active_support/core_ext/string/conversions'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
6
+ # ActiveRecord::Associations::ThroughAssociationScope
7
+ class AliasTracker # :nodoc:
8
+ attr_reader :aliases, :table_joins
9
+
10
+ # table_joins is an array of arel joins which might conflict with the aliases we assign here
11
+ def initialize(table_joins = [])
12
+ @aliases = Hash.new
13
+ @table_joins = table_joins
14
+ end
15
+
16
+ def aliased_table_for(table_name, aliased_name = nil)
17
+ table_alias = aliased_name_for(table_name, aliased_name)
18
+
19
+ if table_alias == table_name
20
+ Arel::Table.new(table_name)
21
+ else
22
+ Arel::Table.new(table_name).alias(table_alias)
23
+ end
24
+ end
25
+
26
+ def aliased_name_for(table_name, aliased_name = nil)
27
+ aliased_name ||= table_name
28
+
29
+ initialize_count_for(table_name) if aliases[table_name].nil?
30
+
31
+ if aliases[table_name].zero?
32
+ # If it's zero, we can have our table_name
33
+ aliases[table_name] = 1
34
+ table_name
35
+ else
36
+ # Otherwise, we need to use an alias
37
+ aliased_name = connection.table_alias_for(aliased_name)
38
+
39
+ initialize_count_for(aliased_name) if aliases[aliased_name].nil?
40
+
41
+ # Update the count
42
+ aliases[aliased_name] += 1
43
+
44
+ if aliases[aliased_name] > 1
45
+ "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
46
+ else
47
+ aliased_name
48
+ end
49
+ end
50
+ end
51
+
52
+ def pluralize(table_name)
53
+ ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
54
+ end
55
+
56
+ private
57
+
58
+ def initialize_count_for(name)
59
+ aliases[name] = 0
60
+
61
+ unless Arel::Table === table_joins
62
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
63
+ quoted_name = connection.quote_table_name(name).downcase
64
+
65
+ aliases[name] += table_joins.map { |join|
66
+ # Table names + table aliases
67
+ join.left.downcase.scan(
68
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
69
+ ).size
70
+ }.sum
71
+ end
72
+
73
+ aliases[name]
74
+ end
75
+
76
+ def truncate(name)
77
+ name[0..connection.table_alias_length-3]
78
+ end
79
+
80
+ def connection
81
+ ActiveRecord::Base.connection
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,231 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+ require 'active_support/core_ext/object/inclusion'
3
+
4
+ module ActiveRecord
5
+ module Associations
6
+ # = Active Record Associations
7
+ #
8
+ # This is the root class of all associations ('+ Foo' signifies an included module Foo):
9
+ #
10
+ # Association
11
+ # SingularAssociation
12
+ # HasOneAssociation
13
+ # HasOneThroughAssociation + ThroughAssociation
14
+ # BelongsToAssociation
15
+ # BelongsToPolymorphicAssociation
16
+ # CollectionAssociation
17
+ # HasAndBelongsToManyAssociation
18
+ # HasManyAssociation
19
+ # HasManyThroughAssociation + ThroughAssociation
20
+ class Association #:nodoc:
21
+ attr_reader :owner, :target, :reflection
22
+
23
+ delegate :options, :to => :reflection
24
+
25
+ def initialize(owner, reflection)
26
+ reflection.check_validity!
27
+
28
+ @target = nil
29
+ @owner, @reflection = owner, reflection
30
+ @updated = false
31
+
32
+ reset
33
+ construct_scope
34
+ end
35
+
36
+ # Returns the name of the table of the related class:
37
+ #
38
+ # post.comments.aliased_table_name # => "comments"
39
+ #
40
+ def aliased_table_name
41
+ reflection.klass.table_name
42
+ end
43
+
44
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
45
+ def reset
46
+ @loaded = false
47
+ IdentityMap.remove(target) if IdentityMap.enabled? && target
48
+ @target = nil
49
+ end
50
+
51
+ # Reloads the \target and returns +self+ on success.
52
+ def reload
53
+ reset
54
+ construct_scope
55
+ load_target
56
+ self unless target.nil?
57
+ end
58
+
59
+ # Has the \target been already \loaded?
60
+ def loaded?
61
+ @loaded
62
+ end
63
+
64
+ # Asserts the \target has been loaded setting the \loaded flag to +true+.
65
+ def loaded!
66
+ @loaded = true
67
+ @stale_state = stale_state
68
+ end
69
+
70
+ # The target is stale if the target no longer points to the record(s) that the
71
+ # relevant foreign_key(s) refers to. If stale, the association accessor method
72
+ # on the owner will reload the target. It's up to subclasses to implement the
73
+ # state_state method if relevant.
74
+ #
75
+ # Note that if the target has not been loaded, it is not considered stale.
76
+ def stale_target?
77
+ loaded? && @stale_state != stale_state
78
+ end
79
+
80
+ # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
81
+ def target=(target)
82
+ @target = target
83
+ loaded!
84
+ end
85
+
86
+ def scoped
87
+ target_scope.merge(@association_scope)
88
+ end
89
+
90
+ # Construct the scope for this association.
91
+ #
92
+ # Note that the association_scope is merged into the target_scope only when the
93
+ # scoped method is called. This is because at that point the call may be surrounded
94
+ # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
95
+ # actually gets built.
96
+ def construct_scope
97
+ if klass
98
+ @association_scope = AssociationScope.new(self).scope
99
+ end
100
+ end
101
+
102
+ # Set the inverse association, if possible
103
+ def set_inverse_instance(record)
104
+ if record && invertible_for?(record)
105
+ inverse = record.association(inverse_reflection_for(record).name)
106
+ inverse.target = owner
107
+ end
108
+ end
109
+
110
+ # This class of the target. belongs_to polymorphic overrides this to look at the
111
+ # polymorphic_type field on the owner.
112
+ def klass
113
+ reflection.klass
114
+ end
115
+
116
+ # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
117
+ # through association's scope)
118
+ def target_scope
119
+ klass.scoped
120
+ end
121
+
122
+ # Loads the \target if needed and returns it.
123
+ #
124
+ # This method is abstract in the sense that it relies on +find_target+,
125
+ # which is expected to be provided by descendants.
126
+ #
127
+ # If the \target is already \loaded it is just returned. Thus, you can call
128
+ # +load_target+ unconditionally to get the \target.
129
+ #
130
+ # ActiveRecord::RecordNotFound is rescued within the method, and it is
131
+ # not reraised. The proxy is \reset and +nil+ is the return value.
132
+ def load_target
133
+ if find_target?
134
+ begin
135
+ if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
136
+ @target = IdentityMap.get(association_class, owner[reflection.foreign_key])
137
+ end
138
+ rescue NameError
139
+ nil
140
+ ensure
141
+ @target ||= find_target
142
+ end
143
+ end
144
+ loaded!
145
+ target
146
+ rescue ActiveRecord::RecordNotFound
147
+ reset
148
+ end
149
+
150
+ private
151
+
152
+ def find_target?
153
+ !loaded? && (!owner.new_record? || foreign_key_present?) && klass
154
+ end
155
+
156
+ def interpolate(sql, record = nil)
157
+ if sql.respond_to?(:to_proc)
158
+ owner.send(:instance_exec, record, &sql)
159
+ else
160
+ sql
161
+ end
162
+ end
163
+
164
+ def creation_attributes
165
+ attributes = {}
166
+
167
+ if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
168
+ attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
169
+
170
+ if reflection.options[:as]
171
+ attributes[reflection.type] = owner.class.base_class.name
172
+ end
173
+ end
174
+
175
+ attributes
176
+ end
177
+
178
+ # Sets the owner attributes on the given record
179
+ def set_owner_attributes(record)
180
+ if owner.persisted?
181
+ creation_attributes.each { |key, value| record[key] = value }
182
+ end
183
+ end
184
+
185
+ # Should be true if there is a foreign key present on the owner which
186
+ # references the target. This is used to determine whether we can load
187
+ # the target if the owner is currently a new record (and therefore
188
+ # without a key).
189
+ #
190
+ # Currently implemented by belongs_to (vanilla and polymorphic) and
191
+ # has_one/has_many :through associations which go through a belongs_to
192
+ def foreign_key_present?
193
+ false
194
+ end
195
+
196
+ # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
197
+ # the kind of the class of the associated objects. Meant to be used as
198
+ # a sanity check when you are about to assign an associated record.
199
+ def raise_on_type_mismatch(record)
200
+ unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
201
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
202
+ raise ActiveRecord::AssociationTypeMismatch, message
203
+ end
204
+ end
205
+
206
+ # Can be redefined by subclasses, notably polymorphic belongs_to
207
+ # The record parameter is necessary to support polymorphic inverses as we must check for
208
+ # the association in the specific class of the record.
209
+ def inverse_reflection_for(record)
210
+ reflection.inverse_of
211
+ end
212
+
213
+ # Is this association invertible? Can be redefined by subclasses.
214
+ def invertible_for?(record)
215
+ inverse_reflection_for(record)
216
+ end
217
+
218
+ # This should be implemented to return the values of the relevant key(s) on the owner,
219
+ # so that when state_state is different from the value stored on the last find_target,
220
+ # the target is stale.
221
+ #
222
+ # This is only relevant to certain associations, which is why it returns nil by default.
223
+ def stale_state
224
+ end
225
+
226
+ def association_class
227
+ @reflection.klass
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,120 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class AssociationScope #:nodoc:
4
+ include JoinHelper
5
+
6
+ attr_reader :association, :alias_tracker
7
+
8
+ delegate :klass, :owner, :reflection, :interpolate, :to => :association
9
+ delegate :chain, :conditions, :options, :source_options, :active_record, :to => :reflection
10
+
11
+ def initialize(association)
12
+ @association = association
13
+ @alias_tracker = AliasTracker.new
14
+ end
15
+
16
+ def scope
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))
24
+
25
+ if options[:through] && !options[:include]
26
+ scope = scope.includes(source_options[:include])
27
+ end
28
+
29
+ if select = select_value
30
+ scope = scope.select(select)
31
+ end
32
+
33
+ add_constraints(scope)
34
+ end
35
+
36
+ private
37
+
38
+ def select_value
39
+ select_value = options[:select]
40
+
41
+ if reflection.collection?
42
+ select_value ||= options[:uniq] && "DISTINCT #{reflection.quoted_table_name}.*"
43
+ end
44
+
45
+ if reflection.macro == :has_and_belongs_to_many
46
+ select_value ||= reflection.klass.arel_table[Arel.star]
47
+ end
48
+
49
+ select_value
50
+ end
51
+
52
+ def add_constraints(scope)
53
+ tables = construct_tables
54
+
55
+ chain.each_with_index do |reflection, i|
56
+ table, foreign_table = tables.shift, tables.first
57
+
58
+ if reflection.source_macro == :has_and_belongs_to_many
59
+ join_table = tables.shift
60
+
61
+ scope = scope.joins(join(
62
+ join_table,
63
+ table[reflection.active_record_primary_key].
64
+ eq(join_table[reflection.association_foreign_key])
65
+ ))
66
+
67
+ table, foreign_table = join_table, tables.first
68
+ end
69
+
70
+ if reflection.source_macro == :belongs_to
71
+ key = reflection.association_primary_key
72
+ foreign_key = reflection.foreign_key
73
+ else
74
+ key = reflection.foreign_key
75
+ foreign_key = reflection.active_record_primary_key
76
+ end
77
+
78
+ if reflection == chain.last
79
+ scope = scope.where(table[key].eq(owner[foreign_key]))
80
+
81
+ conditions[i].each do |condition|
82
+ if options[:through] && condition.is_a?(Hash)
83
+ condition = { table.name => condition }
84
+ end
85
+
86
+ scope = scope.where(interpolate(condition))
87
+ end
88
+ else
89
+ constraint = table[key].eq(foreign_table[foreign_key])
90
+ join = join(foreign_table, constraint)
91
+
92
+ scope = scope.joins(join)
93
+
94
+ unless conditions[i].empty?
95
+ scope = scope.where(sanitize(conditions[i], table))
96
+ end
97
+ end
98
+ end
99
+
100
+ scope
101
+ end
102
+
103
+ def alias_suffix
104
+ reflection.name
105
+ end
106
+
107
+ def table_name_for(reflection)
108
+ if reflection == self.reflection
109
+ # If this is a polymorphic belongs_to, we want to get the klass from the
110
+ # association because it depends on the polymorphic_type attribute of
111
+ # the owner
112
+ klass.table_name
113
+ else
114
+ reflection.table_name
115
+ end
116
+ end
117
+
118
+ end
119
+ end
120
+ end
@@ -1,41 +1,17 @@
1
1
  module ActiveRecord
2
2
  # = Active Record Belongs To Associations
3
3
  module Associations
4
- class BelongsToAssociation < AssociationProxy #:nodoc:
5
- def create(attributes = {})
6
- replace(@reflection.create_association(attributes))
7
- end
8
-
9
- def build(attributes = {})
10
- replace(@reflection.build_association(attributes))
11
- end
12
-
4
+ class BelongsToAssociation < SingularAssociation #:nodoc:
13
5
  def replace(record)
14
- counter_cache_name = @reflection.counter_cache_column
15
-
16
- if record.nil?
17
- if counter_cache_name && !@owner.new_record?
18
- @reflection.klass.decrement_counter(counter_cache_name, previous_record_id) if @owner[@reflection.primary_key_name]
19
- end
20
-
21
- @target = @owner[@reflection.primary_key_name] = nil
22
- else
23
- raise_on_type_mismatch(record)
24
-
25
- if counter_cache_name && !@owner.new_record? && record.id != @owner[@reflection.primary_key_name]
26
- @reflection.klass.increment_counter(counter_cache_name, record.id)
27
- @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
28
- end
6
+ raise_on_type_mismatch(record) if record
29
7
 
30
- @target = (AssociationProxy === record ? record.target : record)
31
- @owner[@reflection.primary_key_name] = record_id(record) unless record.new_record?
32
- @updated = true
33
- end
8
+ update_counters(record)
9
+ replace_keys(record)
10
+ set_inverse_instance(record)
34
11
 
35
- set_inverse_instance(record, @owner)
12
+ @updated = true if record
36
13
 
37
- loaded
38
- record
14
+ self.target = record
39
15
  end
40
16
 
41
17
  def updated?
@@ -43,48 +19,52 @@ module ActiveRecord
43
19
  end
44
20
 
45
21
  private
46
- def find_target
47
- find_method = if @reflection.options[:primary_key]
48
- "find_by_#{@reflection.options[:primary_key]}"
49
- else
50
- "find"
51
- end
52
22
 
53
- options = @reflection.options.dup
54
- (options.keys - [:select, :include, :readonly]).each do |key|
55
- options.delete key
23
+ def update_counters(record)
24
+ counter_cache_name = reflection.counter_cache_column
25
+
26
+ if counter_cache_name && owner.persisted? && different_target?(record)
27
+ if record
28
+ record.class.increment_counter(counter_cache_name, record.id)
29
+ end
30
+
31
+ if foreign_key_present?
32
+ klass.decrement_counter(counter_cache_name, target_id)
33
+ end
56
34
  end
57
- options[:conditions] = conditions
35
+ end
36
+
37
+ # Checks whether record is different to the current target, without loading it
38
+ def different_target?(record)
39
+ record.nil? && owner[reflection.foreign_key] ||
40
+ record.id != owner[reflection.foreign_key]
41
+ end
58
42
 
59
- the_target = @reflection.klass.send(find_method,
60
- @owner[@reflection.primary_key_name],
61
- options
62
- ) if @owner[@reflection.primary_key_name]
63
- set_inverse_instance(the_target, @owner)
64
- the_target
43
+ def replace_keys(record)
44
+ owner[reflection.foreign_key] = record && record[reflection.association_primary_key]
65
45
  end
66
46
 
67
- def foreign_key_present
68
- !@owner[@reflection.primary_key_name].nil?
47
+ def foreign_key_present?
48
+ owner[reflection.foreign_key]
69
49
  end
70
50
 
71
51
  # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
72
52
  # has_one associations.
73
- def we_can_set_the_inverse_on_this?(record)
74
- @reflection.has_inverse? && @reflection.inverse_of.macro == :has_one
53
+ def invertible_for?(record)
54
+ inverse = inverse_reflection_for(record)
55
+ inverse && inverse.macro == :has_one
75
56
  end
76
57
 
77
- def record_id(record)
78
- record.send(@reflection.options[:primary_key] || :id)
58
+ def target_id
59
+ if options[:primary_key]
60
+ owner.send(reflection.name).try(:id)
61
+ else
62
+ owner[reflection.foreign_key]
63
+ end
79
64
  end
80
65
 
81
- def previous_record_id
82
- @previous_record_id ||= if @reflection.options[:primary_key]
83
- previous_record = @owner.send(@reflection.name)
84
- previous_record.nil? ? nil : previous_record.id
85
- else
86
- @owner[@reflection.primary_key_name]
87
- end
66
+ def stale_state
67
+ owner[reflection.foreign_key].to_s
88
68
  end
89
69
  end
90
70
  end