Class: FlexiRecord::Reference

Inherits:
Object
  • Object
show all
Defined in:
lib/flexirecord.rb

Overview

Objects of this class are used to describe a reference between two tables. You can create and register them by calling FlexiRecord::BaseRecord.add_many_to_one_reference or FlexiRecord::RaseRecord.add_one_to_one_reference. For using many-to-many relationships you have to extend the class FlexiRecord::Relationship and create two connected ManyToOneReference’s for that class by calling FlexiRecord::BaseRecord.add_connected_references.

Direct Known Subclasses

ManyToOneReference, OneToOneReference

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source_class, destination_class, column_info, src_to_dst_attr, dst_to_src_attr = nil) ⇒ Reference

Returns a new reference object, describing a relation where objects of the ‘source_class’ refer to objects of the ‘destination_class’. The ‘column_info’ field describes the columns used for that reference. If the ‘column_info’ field is a string, the primary key is used in the destination class, and the primary key prefixed by the string given in ‘column_info’ is used as the foreign key in the source class. If ‘column_info’ is an array, it contains the columns in the source class, followed by the columns in the destination class. The field ‘src_to_dst_attr’ contains the name of the attribute in the source class, which is referring to one object of the destination class. The field ‘dst_to_src_attr’ contains the name of the attribute in the destination class, which is referring to one or many objects of the source class. After the reference has been created, reader, loader and setter functions are added to the ‘source_class’ and ‘destination_class’, to provide access to referenced and referring objects. This method is private, use one of the child classes OneToOneReference or ManyToOneReference, which get the same arguments, to generate references of a given type.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/flexirecord.rb', line 181

def initialize(source_class, destination_class, column_info, src_to_dst_attr, dst_to_src_attr=nil)
  unless source_class.kind_of? Class and destination_class.kind_of? Class
    raise TypeError, "Class expected"
  end
  @source_class      = source_class
  @destination_class = destination_class
  if column_info.respond_to? :to_ary
    column_info = column_info.to_ary.flatten
    unless column_info.length % 2 == 0
      raise ArgumentError, "Flattened 'column_info' array contains odd number of elements."
    end
    @source_columns = column_info[0, column_info.length / 2].collect { |column| column.to_s.dup.freeze }
    @destination_columns = column_info[column_info.length / 2, column_info.length / 2].collect { |column| column.to_s.dup.freeze }
  elsif column_info.respond_to? :to_str
    column_info = column_info.to_str
    @source_columns = []
    @destination_columns = []
    destination_class.primary_columns.each do |column|
      @source_columns << "#{column_info}#{column}".freeze
      @destination_columns << column
    end
  else
    raise ArgumentError, "Array or String expected"
  end
  @source_columns.freeze
  @destination_columns.freeze
  @src_to_dst_attr = src_to_dst_attr ? src_to_dst_attr.to_s.dup.freeze : nil
  @dst_to_src_attr = dst_to_src_attr ? dst_to_src_attr.to_s.dup.freeze : nil
  # Work at the source_class:
  if @src_to_dst_attr
    @source_class.set_loader(@src_to_dst_attr) do |source_records, arguments|
      unless arguments.empty?
        raise ArgumentError, "No extra arguments may be specified for outgoing reference columns."
      end
      destination_records = @destination_class.select_by_value_set(
        @destination_columns,
        source_records.collect { |source_record|
          @source_columns.collect { |column| source_record[column] }
        },
        *arguments
      )
      destination_record_hash = {}
      destination_records.each do |destination_record|
        destination_record_hash[@destination_columns.collect { |column| destination_record[column] }] = destination_record
        if self.one_to_one? and @dst_to_src_attr
          destination_record[@dst_to_src_attr] = nil
        end
      end
      source_records.each do |source_record|
        destination_record = 
          destination_record_hash[@source_columns.collect { |column| source_record[column] }]
        source_record[@src_to_dst_attr, *arguments] = destination_record
        if destination_record and self.one_to_one? and @dst_to_src_attr
          destination_record[@dst_to_src_attr] = source_record
        end
      end
      next destination_records
    end
    @source_columns.each_index do |column_index|
      source_column = @source_columns[column_index]
      destination_column = @destination_columns[column_index]
      @source_class.set_reader(source_column) do |source_record, arguments|
        destination_record = source_record[@src_to_dst_attr]
        next destination_record ? destination_record[destination_column] : source_record[source_column]
      end
      @source_class.set_setter(source_column) do |source_record, value|
        source_record.delete_from_cache(@src_to_dst_attr)
        source_record[source_column] = value
      end
    end
    if self.one_to_one? and @dst_to_src_attr
      @source_class.set_setter(@src_to_dst_attr) do |source_record, value|
        old_destination_record = source_record[@src_to_dst_attr]
        if old_destination_record
          old_destination_record[@dst_to_src_attr] = nil
        end
        source_record[@src_to_dst_attr] = value
        if value
          value[@dst_to_src_attr] = source_record
        end
      end
    end
  end
  # Work at the destination_class:
  if @dst_to_src_attr
    @destination_class.set_loader(@dst_to_src_attr) do |destination_records, arguments|
      unless arguments.empty?
        unless arguments[0].respond_to? :to_str
          raise "First argument of reader method is not a SQL snippet string."
        end
        arguments[0] = arguments[0].to_str
      end
      source_records = @source_class.select_by_value_set(
        @source_columns,
        destination_records.collect { |destination_record|
          @destination_columns.collect { |column| destination_record[column] }
        },
        *arguments
      )
      destination_record_hash = {}
      destination_records.each do |destination_record|
        (destination_record_hash[@destination_columns.collect { |column| destination_record[column] }] ||= []) << destination_record
        destination_record[@dst_to_src_attr, *arguments] =
          if self.many_to_one?
            FlexiRecord::RecordArray.new(@source_class)
          else
            nil
          end
      end
      source_records.each do |source_record|
        matching_destination_records = destination_record_hash[@source_columns.collect { |column| source_record[column] }] || []
        if @src_to_dst_attr
          source_record[@src_to_dst_attr] = matching_destination_records.first
        end
        matching_destination_records.each do |destination_record|
          if self.many_to_one?
            destination_record[@dst_to_src_attr, *arguments] << source_record
          else
            destination_record[@dst_to_src_attr, *arguments] = source_record
          end
        end
      end
      if self.many_to_one?
        destination_records.each do |destination_record|
          destination_record[@dst_to_src_attr, *arguments].freeze
        end
      end
      next source_records
    end
    if self.one_to_one? and @src_to_dst_attr
      @destination_class.set_setter(@dst_to_src_attr) do |destination_record, value|
        old_source_record = destination_record[@dst_to_src_attr]
        if old_source_record
          old_source_record[@src_to_dst_attr] = nil
        end
        destination_record[@dst_to_src_attr] = value
        if value
          value[@src_to_dst_attr] = destination_record
        end
      end
    end
  end
  return self
end

Instance Attribute Details

#destination_classObject (readonly)

Class, whose objects are referred by others.



402
403
404
# File 'lib/flexirecord.rb', line 402

def destination_class
  @destination_class
end

#destination_columnsObject (readonly)

Columns in the referred class, providing a unique or primary key.



408
409
410
# File 'lib/flexirecord.rb', line 408

def destination_columns
  @destination_columns
end

#dst_to_src_attrObject (readonly)

Name (String) of the attribute in the destination class, which is referring to one or many objects of the source class.



414
415
416
# File 'lib/flexirecord.rb', line 414

def dst_to_src_attr
  @dst_to_src_attr
end

#source_classObject (readonly)

Class, whose objects are referring to others.



399
400
401
# File 'lib/flexirecord.rb', line 399

def source_class
  @source_class
end

#source_columnsObject (readonly)

Columns in the referring class, providing the foreign key.



405
406
407
# File 'lib/flexirecord.rb', line 405

def source_columns
  @source_columns
end

#src_to_dst_attrObject (readonly)

Name (String) of the attribute in the source class, which is referring to one object of the destination class.



411
412
413
# File 'lib/flexirecord.rb', line 411

def src_to_dst_attr
  @src_to_dst_attr
end

Instance Method Details

#combine(*arguments) ⇒ Object

Deprecated. Use FlexiRecord::Reference#connect instead.



394
395
396
# File 'lib/flexirecord.rb', line 394

def combine(*arguments)
  self.connect(*arguments)
end

#connect(other_reference, own_attr, other_attr) ⇒ Object

Connects two ManyToOneReference’s to form a many-to-many relation. ‘other_reference’ is another Reference object (as returned by FlexiRecord::Reference.new, ‘own_attr’ is the attribute to be installed in the destination_class of this Reference object for accessing objects of the destination_class of the ‘other_reference’ object, and ‘other_attr’ is the attribute to be installed in the destination_class of the ‘other_reference’ object for accessing objects of the destination_class of this Reference object.



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/flexirecord.rb', line 327

def connect(other_reference, own_attr, other_attr)
  unless other_reference.kind_of? FlexiRecord::Reference
    raise TypeError, "Object of class FlexiRecord::Reference expected."
  end
  reference1 = self
  reference2 = other_reference
  attr1 = own_attr.to_s
  attr2 = other_attr.to_s
  unless self.source_class == other_reference.source_class
    "Combining references having different source classes is not possible."
  end
  relationship_class = self.source_class
  [
    [reference1, reference2, attr1, attr2],
    [reference2, reference1, attr2, attr1]
  ].each do |source_reference, destination_reference, source_attr, destination_attr|
    source_class = source_reference.destination_class
    destination_class = destination_reference.destination_class
    tmp1 = []
    destination_reference.source_columns.each_index do |column_index|
      tmp1 << [
        destination_reference.source_columns[column_index],
        destination_reference.destination_columns[column_index]
      ]
    end
    source_class.set_loader source_attr do |source_records, arguments|
      sql_arguments = arguments.dup
      sql_snippet = sql_arguments.shift
      unless sql_snippet.nil?
        unless sql_snippet.respond_to? :to_str
          raise "First argument of reader method is not a SQL snippet string."
        end
        sql_snippet = sql_snippet.to_str
      end
      destination_records = unless source_records.empty?
        destination_class.sql(
          'SELECT ' << FlexiRecord::DefaultTableAlias << '.* FROM (' <<
          'SELECT "obj".*, ' << relationship_class.columns.collect { |column| '"rel"."' << column << '" AS "_flexirecord_rel_' << column << '"' }.join(', ') << ' FROM ' << relationship_class.table << ' "rel" JOIN ' << destination_class.table << ' "obj" ON ' << tmp1.collect { |tmp1a, tmp1b| '"rel"."' << tmp1a << '" = "obj"."' << tmp1b << '"' }.join(' AND ') << ' WHERE (' << source_reference.source_columns.collect { |column| '"rel"."' << column << '"' }.join(', ') << ') IN (' << source_records.collect { |record| '(' << source_reference.source_columns.collect { '$' }.join(', ') << ')' }.join(', ') << ')' <<
          ') AS ' << FlexiRecord::DefaultTableAlias << ' JOIN ' << relationship_class.table << ' ' << FlexiRecord::RelationshipTableAlias << ' ON ' << relationship_class.primary_columns.collect { |column| '' << FlexiRecord::RelationshipTableAlias << '."' << column << '" = ' << FlexiRecord::DefaultTableAlias << '."_flexirecord_rel_' << column << '"' }.join(' AND ') << ' ' << sql_snippet.to_s,
          *(source_records.collect { |record| source_class.primary_columns.collect { |column| record.read(column) } } + sql_arguments)
        )
      else
        FlexiRecord::RecordArray.new(destination_class)
      end
      destination_record_hash = {}
      destination_records.each do |destination_record|
        (destination_record_hash[
          source_reference.source_columns.collect { |column|
            destination_record['_flexirecord_rel_' << column]
          }
        ] ||= FlexiRecord::RecordArray.new(destination_class)) << destination_record
        relationship_hash = { destination_reference.src_to_dst_attr => destination_record }
        relationship_class.columns.each do |column|
          relationship_hash[column] =
          destination_record.delete_from_cache("_flexirecord_rel_#{column}")
        end
        destination_record[FlexiRecord::RelationshipColumn] = relationship_class.new(relationship_hash)
      end
      source_records.each do |source_record|
        source_record[source_attr, *arguments] = ((destination_record_hash[source_reference.destination_columns.collect { |column| source_record[column] } ]) || FlexiRecord::RecordArray.new(destination_class)).freeze
      end
      next destination_records
    end
  end
end

#many_to_one?Boolean

Returns true, if the object describes a many-to-one relation.

Returns:

  • (Boolean)


422
423
424
# File 'lib/flexirecord.rb', line 422

def many_to_one?
  not @one_to_one
end

#one_to_one?Boolean

Returns true, if the object describes a one-to-one relation.

Returns:

  • (Boolean)


417
418
419
# File 'lib/flexirecord.rb', line 417

def one_to_one?
  @one_to_one
end