181: def initialize(source_class, destination_class, column_info, src_to_dst_attr, dst_to_src_attr=nil)
182: unless source_class.kind_of? Class and destination_class.kind_of? Class
183: raise TypeError, "Class expected"
184: end
185: @source_class = source_class
186: @destination_class = destination_class
187: if column_info.respond_to? :to_ary
188: column_info = column_info.to_ary.flatten
189: unless column_info.length % 2 == 0
190: raise ArgumentError, "Flattened 'column_info' array contains odd number of elements."
191: end
192: @source_columns = column_info[0, column_info.length / 2].collect { |column| column.to_s.dup.freeze }
193: @destination_columns = column_info[column_info.length / 2, column_info.length / 2].collect { |column| column.to_s.dup.freeze }
194: elsif column_info.respond_to? :to_str
195: column_info = column_info.to_str
196: @source_columns = []
197: @destination_columns = []
198: destination_class.primary_columns.each do |column|
199: @source_columns << "#{column_info}#{column}".freeze
200: @destination_columns << column
201: end
202: else
203: raise ArgumentError, "Array or String expected"
204: end
205: @source_columns.freeze
206: @destination_columns.freeze
207: @src_to_dst_attr = src_to_dst_attr ? src_to_dst_attr.to_s.dup.freeze : nil
208: @dst_to_src_attr = dst_to_src_attr ? dst_to_src_attr.to_s.dup.freeze : nil
209:
210: if @src_to_dst_attr
211: @source_class.set_loader(@src_to_dst_attr) do |source_records, arguments|
212: unless arguments.empty?
213: raise ArgumentError, "No extra arguments may be specified for outgoing reference columns."
214: end
215: destination_records = @destination_class.select_by_value_set(
216: @destination_columns,
217: source_records.collect { |source_record|
218: @source_columns.collect { |column| source_record[column] }
219: },
220: *arguments
221: )
222: destination_record_hash = {}
223: destination_records.each do |destination_record|
224: destination_record_hash[@destination_columns.collect { |column| destination_record[column] }] = destination_record
225: if self.one_to_one? and @dst_to_src_attr
226: destination_record[@dst_to_src_attr] = nil
227: end
228: end
229: source_records.each do |source_record|
230: destination_record =
231: destination_record_hash[@source_columns.collect { |column| source_record[column] }]
232: source_record[@src_to_dst_attr, *arguments] = destination_record
233: if destination_record and self.one_to_one? and @dst_to_src_attr
234: destination_record[@dst_to_src_attr] = source_record
235: end
236: end
237: next destination_records
238: end
239: @source_columns.each_index do |column_index|
240: source_column = @source_columns[column_index]
241: destination_column = @destination_columns[column_index]
242: @source_class.set_reader(source_column) do |source_record, arguments|
243: destination_record = source_record[@src_to_dst_attr]
244: next destination_record ? destination_record[destination_column] : source_record[source_column]
245: end
246: @source_class.set_setter(source_column) do |source_record, value|
247: source_record.delete_from_cache(@src_to_dst_attr)
248: source_record[source_column] = value
249: end
250: end
251: if self.one_to_one? and @dst_to_src_attr
252: @source_class.set_setter(@src_to_dst_attr) do |source_record, value|
253: old_destination_record = source_record[@src_to_dst_attr]
254: if old_destination_record
255: old_destination_record[@dst_to_src_attr] = nil
256: end
257: source_record[@src_to_dst_attr] = value
258: if value
259: value[@dst_to_src_attr] = source_record
260: end
261: end
262: end
263: end
264:
265: if @dst_to_src_attr
266: @destination_class.set_loader(@dst_to_src_attr) do |destination_records, arguments|
267: unless arguments.empty?
268: unless arguments[0].respond_to? :to_str
269: raise "First argument of reader method is not a SQL snippet string."
270: end
271: arguments[0] = arguments[0].to_str
272: end
273: source_records = @source_class.select_by_value_set(
274: @source_columns,
275: destination_records.collect { |destination_record|
276: @destination_columns.collect { |column| destination_record[column] }
277: },
278: *arguments
279: )
280: destination_record_hash = {}
281: destination_records.each do |destination_record|
282: destination_record_hash[@destination_columns.collect { |column| destination_record[column] }] = destination_record
283: destination_record[@dst_to_src_attr, *arguments] =
284: if self.many_to_one?
285: FlexiRecord::RecordArray.new(@source_class)
286: else
287: nil
288: end
289: end
290: source_records.each do |source_record|
291: destination_record = destination_record_hash[@source_columns.collect { |column| source_record[column] }]
292: if @src_to_dst_attr
293: source_record[@src_to_dst_attr] = destination_record
294: end
295: if destination_record
296: if self.many_to_one?
297: destination_record[@dst_to_src_attr, *arguments] << source_record
298: else
299: destination_record[@dst_to_src_attr, *arguments] = source_record
300: end
301: end
302: end
303: if self.many_to_one?
304: destination_records.each do |destination_record|
305: destination_record[@dst_to_src_attr, *arguments].freeze
306: end
307: end
308: next source_records
309: end
310: if self.one_to_one? and @src_to_dst_attr
311: @destination_class.set_setter(@dst_to_src_attr) do |destination_record, value|
312: old_source_record = destination_record[@dst_to_src_attr]
313: if old_source_record
314: old_source_record[@src_to_dst_attr] = nil
315: end
316: destination_record[@dst_to_src_attr] = value
317: if value
318: value[@src_to_dst_attr] = destination_record
319: end
320: end
321: end
322: end
323: return self
324: end