sskirby-activerecord 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,79 @@
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 { |h,k| h[k] = initial_count_for(k) }
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
+ if aliases[table_name].zero?
30
+ # If it's zero, we can have our table_name
31
+ aliases[table_name] = 1
32
+ table_name
33
+ else
34
+ # Otherwise, we need to use an alias
35
+ aliased_name = connection.table_alias_for(aliased_name)
36
+
37
+ # Update the count
38
+ aliases[aliased_name] += 1
39
+
40
+ if aliases[aliased_name] > 1
41
+ "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
42
+ else
43
+ aliased_name
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def initial_count_for(name)
51
+ return 0 if Arel::Table === table_joins
52
+
53
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
54
+ quoted_name = connection.quote_table_name(name).downcase
55
+
56
+ counts = table_joins.map do |join|
57
+ if join.is_a?(Arel::Nodes::StringJoin)
58
+ # Table names + table aliases
59
+ join.left.downcase.scan(
60
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
61
+ ).size
62
+ else
63
+ join.left.table_name == name ? 1 : 0
64
+ end
65
+ end
66
+
67
+ counts.sum
68
+ end
69
+
70
+ def truncate(name)
71
+ name.slice(0, connection.table_alias_length - 2)
72
+ end
73
+
74
+ def connection
75
+ ActiveRecord::Base.connection
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,239 @@
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
+ reset_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
+ reset_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
+ # 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 association_scope
97
+ if klass
98
+ @association_scope ||= AssociationScope.new(self).scope
99
+ end
100
+ end
101
+
102
+ def reset_scope
103
+ @association_scope = nil
104
+ end
105
+
106
+ # Set the inverse association, if possible
107
+ def set_inverse_instance(record)
108
+ if record && invertible_for?(record)
109
+ inverse = record.association(inverse_reflection_for(record).name)
110
+ inverse.target = owner
111
+ end
112
+ end
113
+
114
+ # This class of the target. belongs_to polymorphic overrides this to look at the
115
+ # polymorphic_type field on the owner.
116
+ def klass
117
+ reflection.klass
118
+ end
119
+
120
+ # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
121
+ # through association's scope)
122
+ def target_scope
123
+ klass.scoped
124
+ end
125
+
126
+ # Loads the \target if needed and returns it.
127
+ #
128
+ # This method is abstract in the sense that it relies on +find_target+,
129
+ # which is expected to be provided by descendants.
130
+ #
131
+ # If the \target is already \loaded it is just returned. Thus, you can call
132
+ # +load_target+ unconditionally to get the \target.
133
+ #
134
+ # ActiveRecord::RecordNotFound is rescued within the method, and it is
135
+ # not reraised. The proxy is \reset and +nil+ is the return value.
136
+ def load_target
137
+ if find_target?
138
+ begin
139
+ if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
140
+ @target = IdentityMap.get(association_class, owner[reflection.foreign_key])
141
+ end
142
+ rescue NameError
143
+ nil
144
+ ensure
145
+ @target ||= find_target
146
+ end
147
+ end
148
+ loaded! unless loaded?
149
+ target
150
+ rescue ActiveRecord::RecordNotFound
151
+ reset
152
+ end
153
+
154
+ def interpolate(sql, record = nil)
155
+ if sql.respond_to?(:to_proc)
156
+ owner.send(:instance_exec, record, &sql)
157
+ else
158
+ sql
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ def find_target?
165
+ !loaded? && (!owner.new_record? || foreign_key_present?) && klass
166
+ end
167
+
168
+ def creation_attributes
169
+ attributes = {}
170
+
171
+ if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
172
+ attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
173
+
174
+ if reflection.options[:as]
175
+ attributes[reflection.type] = owner.class.base_class.name
176
+ end
177
+ end
178
+
179
+ attributes
180
+ end
181
+
182
+ # Sets the owner attributes on the given record
183
+ def set_owner_attributes(record)
184
+ creation_attributes.each { |key, value| record[key] = value }
185
+ end
186
+
187
+ # Should be true if there is a foreign key present on the owner which
188
+ # references the target. This is used to determine whether we can load
189
+ # the target if the owner is currently a new record (and therefore
190
+ # without a key).
191
+ #
192
+ # Currently implemented by belongs_to (vanilla and polymorphic) and
193
+ # has_one/has_many :through associations which go through a belongs_to
194
+ def foreign_key_present?
195
+ false
196
+ end
197
+
198
+ # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
199
+ # the kind of the class of the associated objects. Meant to be used as
200
+ # a sanity check when you are about to assign an associated record.
201
+ def raise_on_type_mismatch(record)
202
+ unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
203
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
204
+ raise ActiveRecord::AssociationTypeMismatch, message
205
+ end
206
+ end
207
+
208
+ # Can be redefined by subclasses, notably polymorphic belongs_to
209
+ # The record parameter is necessary to support polymorphic inverses as we must check for
210
+ # the association in the specific class of the record.
211
+ def inverse_reflection_for(record)
212
+ reflection.inverse_of
213
+ end
214
+
215
+ # Is this association invertible? Can be redefined by subclasses.
216
+ def invertible_for?(record)
217
+ inverse_reflection_for(record)
218
+ end
219
+
220
+ # This should be implemented to return the values of the relevant key(s) on the owner,
221
+ # so that when state_state is different from the value stored on the last find_target,
222
+ # the target is stale.
223
+ #
224
+ # This is only relevant to certain associations, which is why it returns nil by default.
225
+ def stale_state
226
+ end
227
+
228
+ def association_class
229
+ @reflection.klass
230
+ end
231
+
232
+ def build_record(attributes, options)
233
+ reflection.build_association(attributes, options) do |record|
234
+ record.assign_attributes(create_scope.except(*record.changed), :without_protection => true)
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,119 @@
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, :select))
24
+
25
+ if options[:through] && !options[:include]
26
+ scope = scope.includes(source_options[:include])
27
+ end
28
+
29
+ scope = scope.uniq if options[:uniq]
30
+
31
+ add_constraints(scope)
32
+ end
33
+
34
+ private
35
+
36
+ def add_constraints(scope)
37
+ tables = construct_tables
38
+
39
+ chain.each_with_index do |reflection, i|
40
+ table, foreign_table = tables.shift, tables.first
41
+
42
+ if reflection.source_macro == :has_and_belongs_to_many
43
+ join_table = tables.shift
44
+
45
+ scope = scope.joins(join(
46
+ join_table,
47
+ table[reflection.association_primary_key].
48
+ eq(join_table[reflection.association_foreign_key])
49
+ ))
50
+
51
+ table, foreign_table = join_table, tables.first
52
+ end
53
+
54
+ if reflection.source_macro == :belongs_to
55
+ if reflection.options[:polymorphic]
56
+ key = reflection.association_primary_key(klass)
57
+ else
58
+ key = reflection.association_primary_key
59
+ end
60
+
61
+ foreign_key = reflection.foreign_key
62
+ else
63
+ key = reflection.foreign_key
64
+ foreign_key = reflection.active_record_primary_key
65
+ end
66
+
67
+ conditions = self.conditions[i]
68
+
69
+ if reflection == chain.last
70
+ scope = scope.where(table[key].eq(owner[foreign_key]))
71
+
72
+ 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 = { table.name => condition }
79
+ end
80
+
81
+ scope = scope.where(interpolate(condition))
82
+ end
83
+ else
84
+ constraint = table[key].eq(foreign_table[foreign_key])
85
+
86
+ if reflection.type
87
+ type = chain[i + 1].klass.base_class.name
88
+ constraint = constraint.and(table[reflection.type].eq(type))
89
+ end
90
+
91
+ scope = scope.joins(join(foreign_table, constraint))
92
+
93
+ unless conditions.empty?
94
+ scope = scope.where(sanitize(conditions, table))
95
+ end
96
+ end
97
+ end
98
+
99
+ scope
100
+ end
101
+
102
+ def alias_suffix
103
+ reflection.name
104
+ end
105
+
106
+ def table_name_for(reflection)
107
+ if reflection == self.reflection
108
+ # If this is a polymorphic belongs_to, we want to get the klass from the
109
+ # association because it depends on the polymorphic_type attribute of
110
+ # the owner
111
+ klass.table_name
112
+ else
113
+ reflection.table_name
114
+ end
115
+ end
116
+
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,79 @@
1
+ module ActiveRecord
2
+ # = Active Record Belongs To Associations
3
+ module Associations
4
+ class BelongsToAssociation < SingularAssociation #:nodoc:
5
+ def replace(record)
6
+ raise_on_type_mismatch(record) if record
7
+
8
+ update_counters(record)
9
+ replace_keys(record)
10
+ set_inverse_instance(record)
11
+
12
+ @updated = true if record
13
+
14
+ self.target = record
15
+ end
16
+
17
+ def updated?
18
+ @updated
19
+ end
20
+
21
+ private
22
+
23
+ def find_target?
24
+ !loaded? && foreign_key_present? && klass
25
+ end
26
+
27
+ def update_counters(record)
28
+ counter_cache_name = reflection.counter_cache_column
29
+
30
+ if counter_cache_name && owner.persisted? && different_target?(record)
31
+ if record
32
+ record.class.increment_counter(counter_cache_name, record.id)
33
+ end
34
+
35
+ if foreign_key_present?
36
+ klass.decrement_counter(counter_cache_name, target_id)
37
+ end
38
+ end
39
+ end
40
+
41
+ # Checks whether record is different to the current target, without loading it
42
+ def different_target?(record)
43
+ record.nil? && owner[reflection.foreign_key] ||
44
+ record && record.id != owner[reflection.foreign_key]
45
+ end
46
+
47
+ def replace_keys(record)
48
+ if record
49
+ owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
50
+ else
51
+ owner[reflection.foreign_key] = nil
52
+ end
53
+ end
54
+
55
+ def foreign_key_present?
56
+ owner[reflection.foreign_key]
57
+ end
58
+
59
+ # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
60
+ # has_one associations.
61
+ def invertible_for?(record)
62
+ inverse = inverse_reflection_for(record)
63
+ inverse && inverse.macro == :has_one
64
+ end
65
+
66
+ def target_id
67
+ if options[:primary_key]
68
+ owner.send(reflection.name).try(:id)
69
+ else
70
+ owner[reflection.foreign_key]
71
+ end
72
+ end
73
+
74
+ def stale_state
75
+ owner[reflection.foreign_key].to_s
76
+ end
77
+ end
78
+ end
79
+ end