module CheapImports def self.included(base) base.extend CheapImportsClassMethods # Detect what ORM we are using. if base.superclass.name == "ActiveRecord::Base" @cheap_imports_orm = "ActiveRecord" elsif base.included_modules.collect {|m| m.name }.include?("DataMapper::Resource") @cheap_imports_orm = "DataMapper" base.extend DataMapperMethods else raise "unknown ORM!" end end module DataMapperMethods def column_names properties.collect {|p| p.name.to_s } end def columns properties end end module CheapImportsClassMethods def init_recognizable_hashes default_hash = {} column_names.each do |n| next if n === 'id' default_hash[n.to_sym] = n end raise "default hash is empty" if default_hash.empty? @recognizable_hashes ||= {:default => default_hash} end # Add a new import definition. # # imports :my_descriptive_hash_name => { # :database_column_name => "CORRESPONDING_TABLE_HEADER_TEXT" # } def imports(hash) init_recognizable_hashes @recognizable_hashes.merge!(hash) end # Don't override this. You probably want to override import_rash instead. def import(raw, args) style = recognize_hash_style(raw) # Want to be able to access this data in the raw hash using either the native # key or our nicer symbolic key. @recognizable_hashes[style].each do |k, v| raw[k] = raw[v] end hash = fetch_default_hash(raw, style) hash[:imported_at] = args[:imported_at] if column_names.include?("imported_at") hash[:history] = "Imported from #{style.to_s} at #{args[:imported_at].to_s}." if column_names.include?("history") hash[:source] = style.to_s if column_names.include?("source") import_rash(raw, args, style, hash) end def import_rash(raw, args, style, hash) delete_prior hash[:imported_at] create(hash) end def delete_prior(imported_at) delete_all "imported_at < '#{imported_at}'" if column_names.include?("imported_at") end def fetch_default_hash(raw, style) hash = {} columns.each do |c| column_name = c.name.to_sym next if column_name == :id keys = @recognizable_hashes[style].keys raw_value = nil if keys.include?(column_name) raw_value = value_of_from(column_name, raw) elsif keys.include?("#{name.downcase}_#{column_name}".to_sym) raw_value = value_of_from("#{name.downcase}_#{column_name}".to_sym, raw) end next if raw_value.nil? value = nil type = c.type.to_s.downcase case type when "string", "text", "integer", "boolean" if raw_value === "" value = nil else value = raw_value end when "date", "datetime", "time" date_format_string = @recognizable_hashes[style]["#{column_name}_format".to_sym] date_format_string ||= @recognizable_hashes[style]["#{name.downcase}_#{column_name}_format".to_sym] raise "nil date_format_string for #{style} #{column_name}" if date_format_string.empty? begin value = Date.strptime(raw_value, date_format_string) if type === "date" value = DateTime.strptime(raw_value, date_format_string) if type === "datetime" value = Time.strptime(raw_value, date_format_string) if type === "time" rescue ArgumentError value = nil end when "float", "decimal" value = BigDecimal.new(raw_value.gsub(/[^0-9\.\-]/, '')) else raise "unhandled type: #{c.type.to_s.downcase}" end hash[column_name] = value end hash end # Fetch a row from the raw value hash. def value_of_from(field_name, value_hash) specification = @recognizable_hashes[recognize_hash_style(value_hash)] raise "specification hash should not be nil!" if specification.nil? value = value_hash[specification[field_name]].to_s.strip block_given? ? yield(value) : value end # Can we import this type of hash? # All classes have a default hash so we can always import data which was previously exported from the database. # TODO Get rid of nasty workaround for dated_on_format keys. # Return the name of style, or false if no style is matched. def recognize_hash_style(raw_hash, debug = false) @recognizable_hashes.each do |s, h| puts "trying style: #{s} in class #{self.name}" if debug match = true h.each do |k, v| this_key_matches = (raw_hash.has_key?(v.to_s) || v.to_s =~ /^\%/) puts "key: #{k} matches: #{this_key_matches}" if debug match = match && this_key_matches end puts "overall match: #{match}" if debug return s if match end false end end end class NilClass def id_or_nil nil end end