今天学习一下ActiveRecord的Associations相关的源码,即了解一下我们常用的has_many、has_one、belongs_to、has_and_belongs_to_many的原理
1,activerecord-1.15.3\lib\active_record\associations.rb:
1. require 'active_record/associations/association_proxy'
2. require 'active_record/associations/association_collection'
3. require 'active_record/associations/belongs_to_association'
4. require 'active_record/associations/belongs_to_polymorphic_association'
5. require 'active_record/associations/has_one_association'
6. require 'active_record/associations/has_many_association'
7. require 'active_record/associations/has_many_through_association'
8. require 'active_record/associations/has_and_belongs_to_many_association'
9. require 'active_record/deprecated_associations'
10.
11. module ActiveRecord
12. module Associations
13. module ClassMethods
14. def has_many(association_id, options = {}, &extension)
15. reflection = create_has_many_reflection(association_id, options, &extension)
16.
17. configure_dependency_for_has_many(reflection)
18.
19. if options[:through]
20. collection_reader_method(reflection, HasManyThroughAssociation)
21. else
22. add_multiple_associated_save_callbacks(reflection.name)
23. add_association_callbacks(reflection.name, reflection.options)
24. collection_accessor_methods(reflection, HasManyAssociation)
25. end
26.
27. add_deprecated_api_for_has_many(reflection.name)
28. end
29.
30. def has_one(association_id, options = {})
31. reflection = create_has_one_reflection(association_id, options)
32.
33. module_eval do
34. after_save <<-EOF
35. association = instance_variable_get("@#{reflection.name}")
36. if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
37. association["#{reflection.primary_key_name}"] = id
38. association.save(true)
39. end
40. EOF
41. end
42.
43. association_accessor_methods(reflection, HasOneAssociation)
44. association_constructor_method(:build, reflection, HasOneAssociation)
45. association_constructor_method(:create, reflection, HasOneAssociation)
46.
47. configure_dependency_for_has_one(reflection)
48.
49. # deprecated api
50. deprecated_has_association_method(reflection.name)
51. deprecated_association_comparison_method(reflection.name, reflection.class_name)
52. end
53.
54. def belongs_to(association_id, options = {})
55. if options.include?(:class_name) && !options.include?(:foreign_key)
56. ::ActiveSupport::Deprecation.warn(
57. "The inferred foreign_key name will change in Rails 2.0 to use the association name instead of its class name when they differ. When using :class_name in belongs_to, use the :foreign_key option to explicitly set the key name to avoid problems in the transition.",
58. caller)
59. end
60.
61. reflection = create_belongs_to_reflection(association_id, options)
62.
63. if reflection.options[:polymorphic]
64. association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
65.
66. module_eval do
67. before_save <<-EOF
68. association = instance_variable_get("@#{reflection.name}")
69. if association && association.target
70. if association.new_record?
71. association.save(true)
72. end
73.
74. if association.updated?
75. self["#{reflection.primary_key_name}"] = association.id
76. self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
77. end
78. end
79. EOF
80. end
81. else
82. association_accessor_methods(reflection, BelongsToAssociation)
83. association_constructor_method(:build, reflection, BelongsToAssociation)
84. association_constructor_method(:create, reflection, BelongsToAssociation)
85.
86. module_eval do
87. before_save <<-EOF
88. association = instance_variable_get("@#{reflection.name}")
89. if !association.nil?
90. if association.new_record?
91. association.save(true)
92. end
93.
94. if association.updated?
95. self["#{reflection.primary_key_name}"] = association.id
96. end
97. end
98. EOF
99. end
100.
101. # deprecated api
102. deprecated_has_association_method(reflection.name)
103. deprecated_association_comparison_method(reflection.name, reflection.class_name)
104. end
105.
106. if options[:counter_cache]
107. cache_column = options[:counter_cache] == true ?
108. "#{self.to_s.underscore.pluralize}_count" :
109. options[:counter_cache]
110.
111. module_eval(
112. "after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
113. " unless #{reflection.name}.nil?'"
114. )
115.
116. module_eval(
117. "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
118. " unless #{reflection.name}.nil?'"
119. )
120. end
121. end
122.
123. def has_and_belongs_to_many(association_id, options = {}, &extension)
124. reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
125.
126. add_multiple_associated_save_callbacks(reflection.name)
127. collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
128.
129. old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
130. class_eval <<-end_eval
131. alias_method :#{old_method}, :destroy_without_callbacks
132. def destroy_without_callbacks
133. #{reflection.name}.clear
134. #{old_method}
135. end
136. end_eval
137.
138. add_association_callbacks(reflection.name, options)
139.
140. # deprecated api
141. deprecated_collection_count_method(reflection.name)
142. deprecated_add_association_relation(reflection.name)
143. deprecated_remove_association_relation(reflection.name)
144. deprecated_has_collection_method(reflection.name)
145. end
146.
147. private
148. def association_accessor_methods(reflection, association_proxy_class)
149. define_method(reflection.name) do |*params|
150. force_reload = params.first unless params.empty?
151. association = instance_variable_get("@#{reflection.name}")
152.
153. if association.nil? || force_reload
154. association = association_proxy_class.new(self, reflection)
155. retval = association.reload
156. if retval.nil? and association_proxy_class == BelongsToAssociation
157. instance_variable_set("@#{reflection.name}", nil)
158. return nil
159. end
160. instance_variable_set("@#{reflection.name}", association)
161. end
162.
163. association.target.nil? ? nil : association
164. end
165.
166. define_method("#{reflection.name}=") do |new_value|
167. association = instance_variable_get("@#{reflection.name}")
168. if association.nil?
169. association = association_proxy_class.new(self, reflection)
170. end
171.
172. association.replace(new_value)
173.
174. unless new_value.nil?
175. instance_variable_set("@#{reflection.name}", association)
176. else
177. instance_variable_set("@#{reflection.name}", nil)
178. return nil
179. end
180.
181. association
182. end
183.
184. define_method("set_#{reflection.name}_target") do |target|
185. return if target.nil? and association_proxy_class == BelongsToAssociation
186. association = association_proxy_class.new(self, reflection)
187. association.target = target
188. instance_variable_set("@#{reflection.name}", association)
189. end
190. end
191.
192. def collection_reader_method(reflection, association_proxy_class)
193. define_method(reflection.name) do |*params|
194. force_reload = params.first unless params.empty?
195. association = instance_variable_get("@#{reflection.name}")
196.
197. unless association.respond_to?(:loaded?)
198. association = association_proxy_class.new(self, reflection)
199. instance_variable_set("@#{reflection.name}", association)
200. end
201.
202. association.reload if force_reload
203.
204. association
205. end
206. end
207.
208. def collection_accessor_methods(reflection, association_proxy_class)
209. collection_reader_method(reflection, association_proxy_class)
210.
211. define_method("#{reflection.name}=") do |new_value|
212. # Loads proxy class instance (defined in collection_reader_method) if not already loaded
213. association = send(reflection.name)
214. association.replace(new_value)
215. association
216. end
217.
218. define_method("#{reflection.name.to_s.singularize}_ids") do
219. send(reflection.name).map(&:id)
220. end
221.
222. define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
223. ids = (new_value || []).reject { |nid| nid.blank? }
224. send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
225. end
226. end
227.
228. def association_constructor_method(constructor, reflection, association_proxy_class)
229. define_method("#{constructor}_#{reflection.name}") do |*params|
230. attributees = params.first unless params.empty?
231. replace_existing = params[1].nil? ? true : params[1]
232. association = instance_variable_get("@#{reflection.name}")
233.
234. if association.nil?
235. association = association_proxy_class.new(self, reflection)
236. instance_variable_set("@#{reflection.name}", association)
237. end
238.
239. if association_proxy_class == HasOneAssociation
240. association.send(constructor, attributees, replace_existing)
241. else
242. association.send(constructor, attributees)
243. end
244. end
245. end
246.
247. def create_has_many_reflection(association_id, options, &extension)
248. options.assert_valid_keys(
249. :class_name, :table_name, :foreign_key,
250. :exclusively_dependent, :dependent,
251. :select, :conditions, :include,<img src="/images/forum/smiles/icon_surprised.gif"/>rder, :group, :limit,<img src="/images/forum/smiles/icon_surprised.gif"/>ffset,
252. :as, :through, :source, :source_type,
253. :uniq,
254. :finder_sql, :counter_sql,
255. :before_add, :after_add, :before_remove, :after_remove,
256. :extend
257. )
258.
259. options[:extend] = create_extension_module(association_id, extension) if block_given?
260.
261. create_reflection(:has_many, association_id, options, self)
262. end
263.
264. def create_has_one_reflection(association_id, options)
265. options.assert_valid_keys(
266. :class_name, :foreign_key, :remote, :conditions,<img src="/images/forum/smiles/icon_surprised.gif"/>rder, :include, :dependent, :counter_cache, :extend, :as
267. )
268.
269. create_reflection(:has_one, association_id, options, self)
270. end
271.
272. def create_belongs_to_reflection(association_id, options)
273. options.assert_valid_keys(
274. :class_name, :foreign_key, :foreign_type, :remote, :conditions,<img src="/images/forum/smiles/icon_surprised.gif"/>rder, :include, :dependent,
275. :counter_cache, :extend, :polymorphic
276. )
277.
278. reflection = create_reflection(:belongs_to, association_id, options, self)
279.
280. if options[:polymorphic]
281. reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
282. end
283.
284. reflection
285. end
286.
287. def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
288. options.assert_valid_keys(
289. :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
290. :select, :conditions, :include,<img src="/images/forum/smiles/icon_surprised.gif"/>rder, :group, :limit,<img src="/images/forum/smiles/icon_surprised.gif"/>ffset,
291. :uniq,
292. :finder_sql, :delete_sql, :insert_sql,
293. :before_add, :after_add, :before_remove, :after_remove,
294. :extend
295. )
296.
297. options[:extend] = create_extension_module(association_id, extension) if block_given?
298.
299. reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
300.
301. reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
302.
303. reflection
304. end
305. end
306. end
307. end
该文件主要定义了has_many、has_one、belongs_to、has_and_belongs_to_many这四种方法并将它们分别代理到各个具体的关联类
2,activerecord-1.15.3\lib\active_record\associations\association_collection.rb:
1. module ActiveRecord
2. module Associations
3. class AssociationCollection < AssociationProxy
4.
5. def <<(*records)
6. result = true
7. load_target
8.
9. @owner.transaction do
10. flatten_deeper(records).each do |record|
11. raise_on_type_mismatch(record)
12. callback(:before_add, record)
13. result &&= insert_record(record) unless @owner.new_record?
14. @target << record
15. callback(:after_add, record)
16. end
17. end
18.
19. result && self
20. end
21.
22. alias_method :push, :<<
23. alias_method :concat, :<<
24.
25. def delete_all
26. load_target
27. delete(@target)
28. reset_target!
29. end
30.
31. def delete(*records)
32. records = flatten_deeper(records)
33. records.each { |record| raise_on_type_mismatch(record) }
34. records.reject! { |record| @target.delete(record) if record.new_record? }
35. return if records.empty?
36.
37. @owner.transaction do
38. records.each { |record| callback(:before_remove, record) }
39. delete_records(records)
40. records.each do |record|
41. @target.delete(record)
42. callback(:after_remove, record)
43. end
44. end
45. end
46.
47. def clear
48. return self if length.zero? # forces load_target if hasn't happened already
49.
50. if @reflection.options[:dependent] && @reflection.options[:dependent] == :delete_all
51. destroy_all
52. else
53. delete_all
54. end
55.
56. self
57. end
58.
59. def destroy_all
60. @owner.transaction do
61. each { |record| record.destroy }
62. end
63.
64. reset_target!
65. end
66.
67. def create(attributes = {})
68. if attributes.is_a?(Array)
69. attributes.collect { |attr| create(attr) }
70. else
71. record = build(attributes)
72. record.save unless @owner.new_record?
73. record
74. end
75. end
76.
77. protected
78. def find_target
79. records =
80. if @reflection.options[:finder_sql]
81. @reflection.klass.find_by_sql(@finder_sql)
82. else
83. find(:all)
84. end
85.
86. @reflection.options[:uniq] ? uniq(records) : records
87. end
88. end
89. end
90. end
AssociationCollection是HasOneAssociation、HasManyAssociation、 HasManyThroughAssociation、BelongsToAssociation、 HasAndBelongsToManyAssociation、
BelongsToPolymorphicAssociation这六种关联类的父类,其中定义了关联的<<、create、delete、clear等方法
3,activerecord-1.15.3\lib\active_record\reflection.rb:
1. module ActiveRecord
2. module Reflection
3.
4. module ClassMethods
5. def create_reflection(macro, name, options, active_record)
6. case macro
7. when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
8. reflection = AssociationReflection.new(macro, name, options, active_record)
9. when :composed_of
10. reflection = AggregateReflection.new(macro, name, options, active_record)
11. end
12. write_inheritable_hash :reflections, name => reflection
13. reflection
14. end
15. end
16.
17. class MacroReflection
18. attr_reader :active_record
19. def initialize(macro, name, options, active_record)
20. @macro, @name, @options, @active_record = macro, name, options, active_record
21. end
22. end
23.
24. class AggregateReflection < MacroReflection #:nodoc:
25. def klass
26. @klass ||= Object.const_get(options[:class_name] || class_name)
27. end
28. end
29.
30. class AssociationReflection < MacroReflection #:nodoc:
31. def klass
32. @klass ||= active_record.send(:compute_type, class_name)
33. end
34. end
35.
36. end
37. end
该文件定义了reflection变量以及AggregateReflection、AssociationReflection类,我们在关联类的build方法里可以看到record = @reflection.klass.new(attributes)的调用
4,activerecord-1.15.3\lib\active_record\associations\has_many_association.rb:
- module ActiveRecord
- module Associations
- class HasManyAssociation < AssociationCollection
- def build(attributes = {})
- if attributes.is_a?(Array)
- attributes.collect { |attr| build(attr) }
- else
- record = @reflection.klass.new(attributes)
- set_belongs_to_association_for(record)
-
- @target ||= [] unless loaded?
- @target << record
-
- record
- end
- end
-
- def count(*args)
- if @reflection.options[:counter_sql]
- @reflection.klass.count_by_sql(@counter_sql)
- elsif @reflection.options[:finder_sql]
- @reflection.klass.count_by_sql(@finder_sql)
- else
- column_name, options = @reflection.klass.send(:construct_count_options_from_legacy_args, *args)
- options[:conditions] = options[:conditions].nil? ?
- @finder_sql :
- @finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
- options[:include] = @reflection.options[:include]
-
- @reflection.klass.count(column_name, options)
- end
- end
-
- def find(*args)
- options = Base.send(:extract_options_from_args!, args)
-
- if @reflection.options[:finder_sql]
- expects_array = args.first.kind_of?(Array)
- ids = args.flatten.compact.uniq
-
- if ids.size == 1
- id = ids.first
- record = load_target.detect { |record| id == record.id }
- expects_array ? [ record ] : record
- else
- load_target.select { |record| ids.include?(record.id) }
- end
- else
- conditions = "#{@finder_sql}"
- if sanitized_conditions = sanitize_sql(options[:conditions])
- conditions << " AND (#{sanitized_conditions})"
- end
- options[:conditions] = conditions
-
- if options[:order] && @reflection.options[:order]
- options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
- elsif @reflection.options[:order]
- options[:order] = @reflection.options[:order]
- end
-
- merge_options_from_reflection!(options)
-
- # Pass through args exactly as we received them.
- args << options
- @reflection.klass.find(*args)
- end
- end
-
- protected
- def method_missing(method, *args, &block)
- if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
- super
- else
- create_scoping = {}
- set_belongs_to_association_for(create_scoping)
-
- @reflection.klass.with_scope(
- :create => create_scoping,
- :find => {
- :conditions => @finder_sql,
- :joins => @join_sql,
- :readonly => false
- }
- ) do
- @reflection.klass.send(method, *args, &block)
- end
- end
- end
-
- end
- end
- end
HasManyAssociation等具体的关联类里面就定义了一些自己的find、count、build等方法
总体说来,ActiveRecord首先定义has_many、belongs_to等方法,并利用反射得到这些方法的参数所代表的类,然后把对这些类的操作代理到具体的HasManyAssociation等关联类
了解了ActiveRecord的associations,我们可以轻松定制自己的关联方法和关联类
转载自:http://hideto.javaeye.com/blog/92501