[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Marshal.load does not create new instances?

Ian Trudel

2/27/2009 11:57:00 PM

Marshal does not seem to instantiate given class(es) on load. Moreover,
it will absolutely work as long as the class is define but even if it
has no attributes. The following code snippet creates an array with
filled with objects:

class Data
end

data = File.open("data.bin", "rb") { |f| Marshal.load(f) }

This absolutely works fine even if the Data class used to dump (e.g.
from another program) has many instance variables. They just won't be
accessible directly (but still can be accessible using reflection).
Inspect would show something like <Data:0x2b24088 @name="Account"
@expanded=true ..>. Furthermore, it doesn't seem to use the defined
class if an IO is fed in Marshal.load, thus overriding Data._load just
won't work. Is that the expected behaviour?

This is really annoying considering that I would like to load data onto
one (among many) version of Data class. The version of the class to be
used is determined and set at run-time according to the file loaded;
this is my ultimate goal. There is obviously no cast feature in Ruby.
Even using a proxy won't cut it, if only for the fact that Marshal
doesn't instantiate loaded data.

Any suggestions?

Ian
--
Posted via http://www.ruby-....

16 Answers

7stud --

2/28/2009 3:32:00 AM

0

Ian Trudel wrote:
> Marshal does not seem to instantiate given class(es) on load. Moreover,
> it will absolutely work as long as the class is define
>

Seems pretty standard across programming languages.

>
> This is really annoying considering that I would like to load data onto
> one (among many) version of Data class. The version of the class to be
> used is determined and set at run-time according to the file loaded;
> this is my ultimate goal.
>
> Any suggestions?
>

How about something like this:

-----------
#a program that dumps an object:

class MyData
def greet
puts "hello"
end
end

d = MyData.new
File.open("def1.txt", "w") do |f|
Marshal.dump(d, f)
end
------------

#program that loads the objects:

def1 = <<-def1
class MyData
def greet
puts "hello"
end
end
def1

def2 = <<-def2
class MyData
def greet
puts "goodbye"
end
def shout
puts "HEY"
end
end
def2

def3 = <<-def3
class MyData
def greet
puts "last class"
end
def cry
"Wahhhh wahhh"
end
end
def3

data_classes = {
"def1" => def1,
"def2" => def2,
"def3" => def3
}

print "Enter file name: "
fname = gets.chomp
defname = fname.split(".")[0]
eval(data_classes[defname])

begin
File.open(fname) do |f|
d = Marshal.load(f)
d.greet
d.shout
d.cry
end
rescue NoMethodError
#do nothing
ensure
f.close unless f.nil?
end

--
Posted via http://www.ruby-....

Ian Trudel

2/28/2009 4:27:00 AM

0

7stud -- wrote:

> How about something like this:

> #program that loads the objects:
>
> def1 = <<-def1
> class MyData
> def greet
> puts "hello"
> end
> end
> def1

Your solution seems great (and it works). However, my problem with it is
the necessity to load and eval a class from a heredoc. It would be fine
as long as it is small classes but it won't cut it for lengthy and
numerous classes. I am afraid that it will make development and testing
cycle somehow harder, if only for the fact that I won't have the support
of my favourite IDE since it is treated as text.

I was hoping to have an object-oriented solution, for example, where I
could have a proxy, forwarder/delegator, or even subclass delegation.
These actually did work as long as I don't Marshal.load. Your neat trick
with heredoc and eval would be better used for smaller needs, I think.

Any more suggestion?
--
Posted via http://www.ruby-....

Pit Capitain

2/28/2009 1:29:00 PM

0

2009/2/28 Ian Trudel <ian.trudel@gmail.com>:
> (...)
> Any more suggestion?

Ian, I'm not sure I understand what you want. AFAIK Marshal only works
if you have the same class definitions on both sides. Why is this a
problem for you?

Regards,
Pit

Robert Klemme

2/28/2009 3:20:00 PM

0

2009/2/28 Pit Capitain <pit.capitain@gmail.com>:
> 2009/2/28 Ian Trudel <ian.trudel@gmail.com>:
>> (...)
>> Any more suggestion?
>
> Ian, I'm not sure I understand what you want. AFAIK Marshal only works
> if you have the same class definitions on both sides. Why is this a
> problem for you?

I believe he wants to evolve the class and be able to load data
written with an older (or just different) version of the class. And
now Ian hits the usual problems of schema migration.

Ian, you should be aware of one thing: class definitions are not
serialized - no programming language that I know does this. And there
are probably good reasons (security, efficiency probably).

You can use tricks as 7stud suggested although I feel wary about this.
I would probably choose a different solution based on the
requirements (which are not fully clear to me). If you just need
changing sets of attributes then these options might work:

1. use OpenStruct
2. use Hash
3. change your class Data to store attributes in a single Hash only

There might be other and if you provide more of your requirements we
might come up with other solutions.

Kind regards

robert


--
remember.guy do |as, often| as.you_can - without end

Ian Trudel

2/28/2009 8:51:00 PM

0

> Ian, I'm not sure I understand what you want. AFAIK Marshal only works
> if you have the same class definitions on both sides. Why is this a
> problem for you?

This is actually how I use Marshal. It works fine if I have only one
version in one given Ruby program. The problem resides in loading
different files which may contain one version or another of the given
class definition. There are sometimes additional (or less) instance
variables and methods, different implementation of certain methods, etc.
depending on the version of the class. Mmm. collision problems,
Capitain!

My initial hope was on defining the main class in such way to delegate
to other classes (named and implemented according to its version). In my
twisted mind, I had imagined something that I could set the delegator to
a certain class before loading the data, just like any other proxy, and
then use it; or at least before using methods or accessors.

> Ian, you should be aware of one thing: class definitions are not
> serialized - no programming language that I know does this. And there
> are probably good reasons (security, efficiency probably).

Understandably. :)

> 1. use OpenStruct
> 2. use Hash
> 3. change your class Data to store attributes in a single Hash only

Once again a good idea! Unfortunately, it is not just about data but
also about class and instance methods and their specific implementation.
Would it mean that I could mixin the instance of OStruct with my
specific version of a class (as a module) at that point?


> You can use tricks as 7stud suggested although I feel wary about this.
> I would probably choose a different solution based on the
> requirements (which are not fully clear to me). If you just need
> changing sets of attributes then these options might work:

I have data files generated by different softwares. These files are
generated according to a given class but the implementation (accessors,
methods, etc.) are slightly different according to the software. They
share the same name, basic functionalities and data though they have
differences according to their version. I would like to be able to load
and use them within my Ruby program, any or many of these generated
files at the same time without collision. Requirement was that I do not
have access to the original source of the softwares and I do have to
reimplement and test each version all by myself.

We should perhaps see the problem as if it was extreme: let's imagine
that we have multiple programs which have each a class Data but is
completely different (no similar instance variables nor methods, nothing
in common at all). No access to those programs and yet have to load all
the files within a single Ruby program. What one would do?

Thanks for your help, guys!

Regards,
Ian
--
Posted via http://www.ruby-....

Sean O'Halpin

2/28/2009 10:36:00 PM

0

On Sat, Feb 28, 2009 at 8:50 PM, Ian Trudel <ian.trudel@gmail.com> wrote:
[snip]
> We should perhaps see the problem as if it was extreme: let's imagine
> that we have multiple programs which have each a class Data but is
> completely different (no similar instance variables nor methods, nothing
> in common at all). No access to those programs and yet have to load all
> the files within a single Ruby program. What one would do?

Well, you could dynamically extend the loaded instances with modules
that add the specific required behaviour.
Something like this:

First file represents whatever created the data in the first place:

# file1
class MyData
attr_accessor :kind
attr_accessor :name
def initialize(kind, name)
@kind = kind
@name = name
end
end

instance = MyData.new("Greeting", "World")
data = Marshal.dump(instance)
File.open("data.dat", "wb") do |file|
file.write(data)
end
# end of file1

Second file shows how you could load this data and dynamically decide
how it should behave as an instance:

# file2
# these modules will be used to extend the loaded instance depending
# on its @kind
module Hello
def run
puts "Hello #{ @name }"
end
end

module Goodbye
def run
puts "Goodbye #{ @name }"
end
end

# You need to define this if you're unmarshalling data that has been
# saved as MyData - no way round it as Marshal embeds the class name
# in the data
class MyData
end

# unmarshall data and extend depending on the @kind
data = File.read("data.dat")
instance = Marshal.load(data)
# this is shorthand for determining the nature of the data
if instance.instance_variable_defined?("@kind")
kind = instance.instance_variable_get("@kind")
if Object.const_defined?(kind)
extension = Object.const_get(kind)
instance.extend(extension)
instance.run
else
puts "@kind not known: #{instance.inspect}"
end
else
puts "@kind not defined for: #{instance.inspect}"
end
# end of file2

I'm using @kind as shorthand to stand for something that distinguishes
between instances of your data. (BTW, you can't use Data as a class
name in Ruby - it's reserved for use with C extensions).

HTH,
Regards,
Sean

Gary Wright

2/28/2009 10:42:00 PM

0


On Feb 28, 2009, at 3:50 PM, Ian Trudel wrote:
> We should perhaps see the problem as if it was extreme: let's imagine
> that we have multiple programs which have each a class Data but is
> completely different (no similar instance variables nor methods,
> nothing
> in common at all). No access to those programs and yet have to load
> all
> the files within a single Ruby program. What one would do?

You are establishing ground rules that can't be followed.

If you have two programs that want to exchange data then they've got
to have some pre-existing *shared* understanding of the structure
of the data. You can't migrate the state of an object from one
arbitrary class to another arbitrary class without constraining the
form of that state in some way.

Ruby's marshal has a built-in assumption that the class that loads
the object state is the *same* (for some reasonable definition of
"same") as the class that dumps the object state.

It's sounds to me like you need to abstract out the state into its
own class and use Marshal to serialize/deserialize that and then
devise import/export methods for the various 'versions' of your Data
class. Use an intermediate class to act as the adapter between
all the versions of your Data class.

Gary Wright




Sean O'Halpin

3/1/2009 12:59:00 AM

0

Oops. That should be:

instance = MyData.new("Hello", "World")

in the first file.

Brian Candler

3/1/2009 9:03:00 PM

0

Ian Trudel wrote:
>> Ian, I'm not sure I understand what you want. AFAIK Marshal only works
>> if you have the same class definitions on both sides. Why is this a
>> problem for you?
>
> This is actually how I use Marshal. It works fine if I have only one
> version in one given Ruby program. The problem resides in loading
> different files which may contain one version or another of the given
> class definition. There are sometimes additional (or less) instance
> variables

Instance variables are not part of the class definition at all - even
when you're only talking about a single version of the class. Instance
variables are dynamically set within each object instance. For example:

class Foo
def bar
@xyz = 123
end
end

f = Foo.new # no instance variables set at all

g = Foo.new
g.instance_variable_set(:@baz, 999) # only @baz is set

Given this: it makes sense that serializing or deserializing an instance
of Foo only takes into account what instance variables are set in that
particular object, making no reference to the class definition.
--
Posted via http://www.ruby-....

Michael Fellinger

3/2/2009 5:33:00 PM

0

On Sat, Feb 28, 2009 at 8:56 AM, Ian Trudel <ian.trudel@gmail.com> wrote:
> Marshal does not seem to instantiate given class(es) on load. Moreover,
> it will absolutely work as long as the class is define but even if it
> has no attributes. The following code snippet creates an array with
> filled with objects:
>
> class Data
> end
>
> data = File.open("data.bin", "rb") { |f| Marshal.load(f) }
>
> This absolutely works fine even if the Data class used to dump (e.g.
> from another program) has many instance variables. They just won't be
> accessible directly (but still can be accessible using reflection).
> Inspect would show something like <Data:0x2b24088 @name="Account"
> @expanded=true ..>. Furthermore, it doesn't seem to use the defined
> class if an IO is fed in Marshal.load, thus overriding Data._load just
> won't work. Is that the expected behaviour?
>
> This is really annoying considering that I would like to load data onto
> one (among many) version of Data class. The version of the class to be
> used is determined and set at run-time according to the file loaded;
> this is my ultimate goal. There is obviously no cast feature in Ruby.
> Even using a proxy won't cut it, if only for the fact that Marshal
> doesn't instantiate loaded data.

http://eigenclass.org/R2/writings/extprot-vs-ru...

^ manveru