/
associations.rb
152 lines (122 loc) · 5.66 KB
/
associations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# FIXME load paths
require File.dirname(__FILE__) + '/associations/association_proxy'
require File.dirname(__FILE__) + '/associations/association_collection'
require File.dirname(__FILE__) + '/associations/has_many_association'
module ActiveRecord
module Associations
module ClassMethods
def has_many(association_id, options = {}, &extension) #:nodoc:
reflection = create_has_many_reflection(association_id, options, &extension)
configure_dependency_for_has_many(reflection)
add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
add_multiple_associated_save_callbacks(reflection.name)
add_association_callbacks(reflection.name, reflection.options)
if options[:through]
collection_accessor_methods(reflection, HasManyThroughAssociation, options)
else
collection_accessor_methods(reflection, HasManyAssociation, options)
end
add_has_many_cache_callbacks if options[:cached]
end
alias_method :active_record_belongs_to, :belongs_to
def belongs_to(association_id, options = {}) #:nodoc:
active_record_belongs_to(association_id, options)
add_belongs_to_cache_callbacks(association_id) if options[:cached]
end
def collection_reader_method(reflection, association_proxy_class, options)
define_method(reflection.name) do |*params|
ivar = "@#{reflection.name}"
force_reload = params.first unless params.empty?
association = if options[:cached]
cache_read(reflection)
else
instance_variable_get(ivar) if instance_variable_defined?(ivar)
end
unless association.respond_to?(:loaded?)
association = association_proxy_class.new(self, reflection)
if options[:cached]
cache_write(reflection, association)
else
instance_variable_set(ivar, association)
end
end
if force_reload
association.reload
cache_write(reflection, association) if options[:cached]
end
association
end
method_name = "#{reflection.name.to_s.singularize}_ids"
define_method(method_name) do
if options[:cached]
cache_fetch("#{cache_key}/#{method_name}", send("calculate_#{method_name}"))
elsif send(reflection.name).loaded? || reflection.options[:finder_sql]
send("calculate_#{method_name}")
else
send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id)
end
end
define_method("calculate_#{method_name}") do
send(reflection.name).map(&:id)
end
end
def has_and_belongs_to_many(association_id, options = {}, &extension) #:nodoc:
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
add_multiple_associated_save_callbacks(reflection.name)
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation, options)
# Don't use a before_destroy callback since users' before_destroy
# callbacks will be executed after the association is wiped out.
old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
class_eval <<-end_eval unless method_defined?(old_method)
alias_method :#{old_method}, :destroy_without_callbacks
def destroy_without_callbacks
#{reflection.name}.clear
#{old_method}
end
end_eval
add_association_callbacks(reflection.name, options)
end
def collection_accessor_methods(reflection, association_proxy_class, options, writer = true)
collection_reader_method(reflection, association_proxy_class, options)
if writer
define_method("#{reflection.name}=") do |new_value|
# Loads proxy class instance (defined in collection_reader_method) if not already loaded
association = send(reflection.name)
association.replace(new_value)
cache_write(reflection, association) if options[:cached]
association
end
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
ids = (new_value || []).reject { |nid| nid.blank? }
send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
end
end
end
valid_keys_for_has_many_association << :cached
valid_keys_for_belongs_to_association << :cached
def add_has_many_cache_callbacks
method_name = :has_many_after_save_cache_expire
return if respond_to? method_name
define_method(method_name) do
return unless self[:updated_at]
self.class.reflections.each do |name, reflection|
cache_delete(reflection) if reflection.options[:cached]
end
end
after_save method_name
end
def add_belongs_to_cache_callbacks(reflection_name)
after_save_method_name = "belongs_to_after_save_for_#{reflection_name}".to_sym
after_destroy_method_name = "belongs_to_after_destroy_for_#{reflection_name}".to_sym
return if respond_to? after_save_method_name
define_method(after_save_method_name) do
send(reflection_name).expire_cache_for(self.class.name)
end
alias_method after_destroy_method_name, after_save_method_name
after_save after_save_method_name
after_destroy after_destroy_method_name
end
end
end
end