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
153
154
155
156
157
158
159
160
161
162
163
|
# File 'lib/class_table_inheritance.rb', line 37
def has_extra_columns_from(options, &block)
table_name = options[:table]
primary_key_name = options[:foreign_key] || "#{name.demodulize.underscore}_id"
proxy_class_name = options[:proxy_class_name] || "ExtraColumnsFor#{table_name.camelize}"
proxy_class_namespace = (options[:proxy_class_namespace] || name).constantize
proxy_symbol = "extra_columns_for_#{table_name}".to_sym
proxy_class = proxy_class_namespace.const_get(proxy_class_name) rescue nil
unless proxy_class
proxy_class = proxy_class_namespace.const_set(proxy_class_name, Class.new(ActiveRecord::Base))
proxy_class.class_eval do
set_table_name table_name
set_primary_key primary_key_name
def self.reloadable?; false; end
end
proxy_class.class_eval(&block) if block_given?
end
has_one_without_cti proxy_symbol, :class_name => proxy_class.name, :foreign_key => primary_key_name, :dependent => true
if options[:save_before_superclass_callbacks] || true
proxy_save_callback = @inheritable_attributes[:after_save].pop
@inheritable_attributes[:after_create] ||= []
@inheritable_attributes[:after_create].unshift(proxy_save_callback)
@inheritable_attributes[:after_update] ||= []
@inheritable_attributes[:after_update].unshift(proxy_save_callback)
end
self.default_eager_loading = [proxy_symbol] unless options[:default_eager_loading] == false
class_eval <<-EOV
alias_method :#{proxy_symbol}_old, :#{proxy_symbol}
def #{proxy_symbol}
#{proxy_symbol}_old or self.#{proxy_symbol} = #{proxy_class.name}.new
end
def save(*args)
self.#{proxy_symbol} ||= #{proxy_class.name}.new
super
end
# this doesn't happen automatically on update, so we'll make it:
after_update {|record| record.#{proxy_symbol}.save }
alias_method :write_attribute_super, :write_attribute
def write_attribute(attr_name, attr_value)
if self.class.columns.find {|c| c.name == attr_name}
write_attribute_super(attr_name, attr_value)
else
self.#{proxy_symbol}.write_attribute(attr_name, attr_value)
end
end
alias_method :old_attributes, :attributes
def attributes
all_attributes = old_attributes.update(self.#{proxy_symbol}.attributes)
all_attributes.delete(#{proxy_class_name}.primary_key) if self.class.primary_key != #{proxy_class_name}.primary_key
all_attributes
end
class << self
alias_method :method_missing_super, :method_missing
def method_missing(method_symbol, *parameters)
if method_symbol.to_s =~ /^find_by_(.+)/
attributes = $1.split('_and_')
super_attributes = attributes & columns.map {|c| c.name}
sub_attributes = attributes - super_attributes
if super_attributes.size > 0
# TODO (uwe): Only use super attributes first
# then filter by sub_attributes
method_missing_super(method_symbol, parameters)
else
#{proxy_class_name}.method_missing(method_symbol, parameters)
end
else
method_missing_super(method_symbol, parameters)
end
end
delegate :foreign_keys, :validates_uniqueness_of, {:to => #{proxy_class_name}}
end
EOV
delegate_methods = proxy_class.column_names
delegate_methods += proxy_class.column_names.map {|name| "#{name}=".to_sym }
delegate_methods += proxy_class.column_names.map {|name| "#{name}?".to_sym }
proxy_class.reflect_on_all_associations.each do |reflection|
delegate_methods += case reflection.macro
when :has_many, :has_and_belongs_to_many
[reflection.name, "#{reflection.name}=".to_sym, "#{reflection.name.to_s.singularize}_ids=".to_sym]
when :has_one
[reflection.name, "#{reflection.name}=".to_sym, "build_#{reflection.name}".to_sym, "create_#{reflection.name}".to_sym]
when :belongs_to
[reflection.name, "#{reflection.name}=".to_sym, "#{reflection.name}?".to_sym, "build_#{reflection.name}".to_sym, "create_#{reflection.name}".to_sym]
end
end
delegate *(delegate_methods << {:to => proxy_symbol})
proxy_class.belongs_to(:base, :class_name => options[:proxy_class_belongs_to], :foreign_key => primary_key_name) if options[:proxy_class_belongs_to]
if options[:delegate_new_associations]
class_eval <<-EOV
# associations on this subclass get added to the proxy class, and then the relevant methods delegated to the proxy object
def self.belongs_to(name, *params)
ExtraColumns.belongs_to(name, *params)
delegate name, "\#{name}=".to_sym, "\#{name}?".to_sym, "build_\#{name}".to_sym, "create_\#{name}".to_sym, :to => :#{proxy_symbol}
end
def self.has_one(name, *params)
ExtraColumns.has_one(name, *params)
delegate name, "\#{name}=".to_sym, "build_\#{name}".to_sym, "create_\#{name}".to_sym, :to => :#{proxy_symbol}
end
def self.has_many(name, *params)
ExtraColumns.has_many(name, *params)
delegate name, "\#{name}=".to_sym, "\#{name.to_s.singularize}_ids=".to_sym, :to => :#{proxy_symbol}
end
def self.has_and_belongs_to_many(name, *params)
ExtraColumns.has_and_belongs_to_many(name, *params)
delegate name, "\#{name}=".to_sym, "\#{name.to_s.singularize}_ids=".to_sym, :to => :#{proxy_symbol}
end
EOV
end
end
|