tags:

views:

148

answers:

5

Hi,

I have a curiosity question. If I have a ruby class and then I dynamically add class methods, class variables, etc. to it during execution is there anyway for me to save the altered class definition so that next time I start my application I can use it again?

A: 

You're editing the class on the fly, and you want to save that? You could try using the Marshal module, it'll allow you to save objects to a file, and read them back in memory dynamically.

cloudhead
+1  A: 

http://ruby-doc.org/core/classes/Marshal.html

That wont work for dynamically added methods.
toholio
+4  A: 

There is no built-in way to do this. Marshal can't save methods. If these methods and variables are generated in some systematic way, you could save the data necessary for the class to recreate them. For example, if you have a make_special_method(purpose, value) method that defines these methods, create an array of the arguments you need to pass to these methods and read it in when you want to reconstitute the state of the class.

Chuck
+1  A: 

Simply marshalling the object (as others have said) wont work. Lets look at an example. Consider this class:

class Extras
  attr_accessor :contents
  def test
    puts "This instance of Extras is OK. Contents is: " + @contents.to_s
  end

  def add_method( name )
    self.class.send :define_method, name.to_sym do
      puts "Called " + name.to_s
    end
  end
end

Now lets write a program which creates an instance, adds a method to it and saves it to disk:

require 'extras'

fresh = Extras.new
fresh.contents = 314
fresh.test # outputs "This instance of Extras is OK. Contents is: 314"
fresh.add_method( :foo )
fresh.foo # outputs "Called foo"

serial = Marshal.dump( fresh )
file = File.new "dumpedExample", 'w'
file.write serial

So we can call the normal method 'test' and the dynamic method 'foo'. Lets look at what happens if we write a program which loads the instance of Example which was saved to disk:

require 'extras'

file = File.new 'dumpedExample', 'r'
serial = file.read

reheated = Marshal.load( serial )
reheated.test # outputs "This instance of Extras is OK. Contents is 314"
reheated.foo # throws a NoMethodError exception

So we can see that while the instance (including the values of member variables) was saved the dynamic method was not.

From a design point of view it's probably better to put all your added code into a module and load that into the class again when you next run the program. We'd need a good example of how you might want to use it though to really know this.

If you need extra information to recreate the methods then have the module save these as member variables. Implement included in the module and have it look for these member variables when it is included into the class.

toholio
+2  A: 

Depending on what exactly you mean, there are a couple of ways to go about this.

The simplest case is the one where you've added variables or methods to an already-existing class, as in this example:

class String
  def rot13
    return self.tr('a-z', 'n-za-m')
  end
end

Here we've added the rot13 method to class String. As soon as this code is run, every String everywhere in your program will be able to #rot13. Thus, if you have some code that needs rot13-capable strings, you just ensure that the code above is run before the code in question, e.g. by putting the rot13 code in a file someplace and require()ing it. Very easy!

But maybe you've added a class variable to a class, and you want to preserve not just its existence but its value, as in:

class String
  @@number_of_tr_calls_made = 0
  # Fix up #tr so that it increments @@number_of_tr_calls_made
end

Now if you want to save the value of @@number_of_tr_calls_made, you can do so in the same way you would with any other serializable Ruby value: via the Marshal library. Also easy!

But something in the way you phrased your question makes me suspect that you're doing something like this:

greeting = "Hello"
class <<greeting
  def rot13
    return self.tr('a-z', 'n-za-m')
  end
end
encrypted_greeting = greeting.rot13

This is very different from what we did in the first example. That piece of code gave every String in your program the power to rot13 itself. This code grants that power to only the object referred to by the name 'greeting'. Internally, Ruby does this by creating an anonymous Singleton subclass of String, adding the rot13 method to it, and changing greeting's class to that anonymous subclass.

The problem here is that Singletons can't be Marshal'd (to see why, try to figure out how to maintain the Singleton invariant when any call to Marshal.load can generate copies of extant Singleton objects). Now greeting has a Singleton in its inheritance hierarchy, so if you want to save and load it you are hosed. Make a subclass instead:

class HighlySecurableString < String
  def rot13
    return self.tr('a-z', 'n-za-m')
  end
end
greeting = HighlySecurableString.new("hello")
David Seiler
I think the asker was referring to methods added to a class during execution. I.e. metaprogrammed methods. I may have misunderstood.
toholio