module TaliaCore # Contains all data types that are handled by the Talia system. All data elements # should be subclasses of DataRecord. Records that have data files attached are # subclasses of FileRecord module DataTypes # Base class for all data records in Talia. This only contains a basic interface, # without much functionality. All data-related methods will return a # NotImplementedError # # The DataRecord provides an interface to access a generic array/buffer of bytes, # with the base class not making any assumptions on how these bytes are stored. # # Subclasses should usually provide the inferface of this class, which is more # or less like the standard file interface. # # Each data record has a "location" field, which is roughly equivalent to the file # name, and a MIME type. The default behaviour is that, if not set manually, # the MIME type is automatically set before saving. It will be determined by # the "file extension" of the location field. # # Each data record must belong to an ActiveSource. For more information on how to # handle records with files, see the FileRecord class class DataRecord < ActiveRecord::Base # Attention: These need to come before the extends, otherwise it'll blow the # tests belongs_to :source, :class_name => 'TaliaCore::ActiveSource' before_create :set_mime_type # Mime type must be saved before the record is written # validates_presence_of :source # Declaration of main abstract methods ====================== # Some notes: every subclasses of DataRecord must implement # at least the following methods # See also: single-table inheritance # returns all bytes in the object as an array of unsigned integers def all_bytes raise NotImplementedError end # Returns all_bytes as an binary string def content_string all_bytes.pack('C*') if(all_bytes) end # returns the next byte from the object, or nil at EOS def get_byte(close_after_single_read=false) raise NotImplementedError end # returns the current position of the read cursor def position raise NotImplementedError end # adjust the position of the read cursor def seek(new_position) raise NotImplementedError end # returns the size of the object in bytes def size raise NotImplementedError end # reset the cursor to the initial state def reset end def extract_mime_type(location) # Lookup the mime type for the extension (removing the dot # in front of the file extension) Works only for the file # types supported by Rails' Mime class. Mime::Type.lookup_by_extension((File.extname(location).downcase)[1..-1]).to_s end def mime_type self.mime end attr_accessor :temp_path # class methods ============================================ class << self # Find all data records about a specified source def find_data_records(id) find(:all, :conditions => { :source_id => id }) end def find_by_type_and_location!(source_data_type, location) # TODO: Should it directly instantiate the STI sub-class? # In this case we should use the following line instead. # # source_data = source_data_type.classify.constantize.find_by_location(location, :limit => 1) # data_type = "TaliaCore::DataTypes::#{source_data_type.camelize}" source_data = self.find(:first, :conditions => ["type = ? AND location = ?", data_type, location]) raise ActiveRecord::RecordNotFound if source_data.nil? source_data end end private # Returns demodulized class name. def class_name self.class.name.demodulize end # set mime type if it hasn't been assigned already def set_mime_type assit(!self.location.blank?, "Location for #{self} should not be blank") if(!self.location.blank? && self.mime.blank?) # Set mime type for the record self.mime = extract_mime_type(self.location) assit_not_nil(self.mime, "Mime should not be nil (location was #{self.location})!") end end end end end