1.4.1.15. fejezet, Marshal avagy objektumok tárolása másként

Hasonlítsuk össze az alábbi kódot az előző fejezetben tanultakkal:

f = File.open( 'friends.sav', 'w' )
Marshal.dump( ["fred", "bert", "mary"], f )
f.close
 
File.open( 'morefriends.sav', 'w' ){ |friendsfile|	
    Marshal.dump( ["sally", "agnes", "john" ], friendsfile )
}
 
File.open( 'morefriends.sav' ){ |f|	
    $arr = Marshal.load(f)
}
 
myfriends = Marshal.load(File.open( 'friends.sav' ))
morefriends = Marshal.load(File.open( 'morefriends.sav' ))
 
p( myfriends )
p( morefriends )
p( $arr )

Ugye, hogy mennyire hasonló? Csak a YAML helyére Marshal-t írtunk. Az eredménye pedig itt van:

["fred", "bert", "mary"]
["sally", "agnes", "john"]
["sally", "agnes", "john"]

Ha belenézünk egy fájlba, pl.: friends.sav, látni fogjuk, hogy a YAML-el szemben ez egy bináris állomány. Néhány szövegrész olvasható, de nem lenne egyszerű egy szövegszerkesztővel módosítani. Ugyan olyan könnyű sorozatokat gyártani akármelyik adattípusból és adatstruktúrákból a legfelső szintű objektum mentésével, mint YAML-ben. A különbség az IO osztály használatakor (példányosításakor) és az objektum-egyedi metódusok mentésekor keletkező TypeError kivételben érhető tetten. Megnézzük ezt az objektum-egyedi metódusoknál.

Változók elhagyása mentéskor

Mint a YAML-nél, itt is meghatározható, melyik tulajdonság kerüljön ki mentéskor. A marshal_dump metódus felülírása szükséges ehhez:

def marshal_dump
  [@num, @arr]
end

A marshal-nál még egy metódust felül kell írjunk. Ez a marshal_load, ami az objektum tulajdonságait visszatölti az újonnan létrehozott objektumba:

class Mclass 
  def initialize(aNum, aStr, anArray)
    @num = aNum
    @str = aStr
    @arr = anArray
 end
 
 def marshal_dump
   [@num, @arr]
 end
 
 def marshal_load(data)
   @num = data[0] 
   @arr = data[1]
   @str = "default string"
 end
end
 
ob = Mclass.new( 100, "fred", [1,2,3] )
p( ob )
 
marshal_data = Marshal.dump( ob )
ob2 = Marshal.load( marshal_data ) 
p( ob2 )

Eredménye:

#<Mclass:0x9005524 @num=100, @str="fred", @arr=[1, 2, 3]>
#<Mclass:0x90053bc @num=100, @str="default string", @arr=[1, 2, 3]>

Megjegyzem: ahogy itt a memóriában, a lemezen is működik ugyan ezzel a technikával.

Egyedi objektum mentése

Marshal módon egyedi objektumot nem lehet menteni, kivételt generál a mentés.

  1. ob = Object.new
  2. class << ob
  3.   def xxx( aStr )
  4.   @x = aStr
  5.   end
  6. end
  7.  
  8. ob.xxx( "hello" )
  9. p( ob )
  10.  
  11. File.open( 'test.sav', 'w' ){ |f|
  12. Marshal.dump( ob, f )
  13. }
  14.  
  15. File.open( 'test.sav' ){ |f|
  16. ob = Marshal.load(f)
  17. }
  18.  
  19. p( ob )

Eredménye:

#<Object:0x8ccc7e0 @x="hello">
singleton_m.rb:12:in `dump': singleton can't be dumped (TypeError)
singleton_m.rb:12:in `block in <main>'
singleton_m.rb:11:in `open'
singleton_m.rb:11:in `<main>'

Ugyanez YAML-ben félig működik.

require 'yaml'
 
ob = Object.new
 
# singleton class
class << ob
  def xxx( aStr )
    @x = aStr
  end	
end
ob.xxx( "hello world" )
p( ob )
 
File.open( 'test.yml', 'w' ){ |f|	
  YAML.dump( ob, f )
}
 
ob.xxx( "new string" )
p( ob )
 
File.open( 'test.yml' ){ |f|	
  ob = YAML.load(f)
}
 
p( ob )

Eredménye:

#<Object:0x8f204d8 @x="hello world">
#<Object:0x8f204d8 @x="new string">
#<Object:0x8f1b7e4 @x="hello world">

Ha megnézzük a YAML fájlt, ezt látjuk:

--- !ruby/object 
x: hello world

Ha megnézzük az futási eredményt, azt látjuk, hogy van ugyan egy x példány változónk, de nincs meg az egyedi objektumban definiált xxx metódusunk. Tehát habár a YAML jóval engedékenyebb az adat mentésnél és betöltésnél, nem hozza teljesen létre az egyedi objektumot amikor visszatölti az adatokat.

Ami működik Marshal módon, az kicsit bonyolultabb:

FILENAME = 'test2.sav'
 
class X
  def marshal_dump
    [@x]
  end
 
  def marshal_load(data)
    @x = data[0]
  end
end
 
def makeIntoSingleton( someOb )
  class << someOb
    def xxx=( aStr )
      @x = aStr
    end	
 
    def xxx
      return @x
    end
  end
  return someOb
end
 
ob = X.new
 
if File.exists?(FILENAME) then
  File.open(FILENAME){ |f|	
    ob = Marshal.load(f)
  }
else
  puts( "Saved data can't be found" )
end
 
p( ob )
 
ob = makeIntoSingleton( ob )
 
if ob.xxx == "hello" then 
  ob.xxx = "goodbye"
else
  ob.xxx = "hello"
end
 
File.open( FILENAME, 'w' ){ |f|	
  Marshal.dump( ob, f )
}
 
p( ob )
puts( ob.xxx )

Az eredmény pedig:

#<X:0x84305b4 @x="hello">
#<X:0x84305b4 @x="goodbye">
goodbye

Még egy összehasonlítást nézzünk meg. Az elmentett dump verziója jó, ha megegyezik az aktuálisan használttal, különben bonyodalmak keletkezhetnek, amik kivételeket generálhatnak. Hasonlítsuk össze a jelenleg használtat a fájlban tárolttal. Hogy hol tárolja a fájlt? Az első két bájtban. Ez is egy kis körüljárást igényel, ugyanis 1.8-ban a File.getc() fixnum-ot ad vissza, 1.9-ben már 1 byte hosszú stringet. Tehát a getc egyszerűbb lenne, de TypeError kivétel keletkezik, ha használjuk. Az interneten azt találtam, hogy a String osztálynak van egy unpack metódusa, amivel byte hosszú String értéket alakíthatok számmá. Nézzük hogy sikerült:

f = File.open('test2.sav')
 
vMajor = f.read(1).unpack('c').join().to_i
 #getc helyett
vMinor = f.read(1).unpack('c').join().to_i
 #getc helyett
f.close
 
puts("File: Marshal Version=#{vMajor}:#{vMinor}" )
puts("Current Marshal Version=#{Marshal::MAJOR_VERSION}:#{Marshal::MINOR_VERSION}"  )
 
if vMajor == Marshal::MAJOR_VERSION then
    puts( "Major version number is compatible" )
  if vMinor == Marshal::MINOR_VERSION then
    puts( "Minor version number is compatible" )
  elsif vMinor < Marshal::MINOR_VERSION then
    puts( "Minor version is lower - old file format" )
  else
    puts( "Minor version is higher - newer file format" )		
  end
else
  puts( "Major version number is incompatible" )
end

És az eredmény:

File: Marshal Version=4:8
Current Marshal Version=4:8
Major version number is compatible
Minor version number is compatible

Ez visszafelé is kompatibilis.