Programming C, C++, Java, PHP, Ruby, Turing, VB
Computer Science Canada 
Programming C, C++, Java, PHP, Ruby, Turing, VB  

Username:   Password: 
 RegisterRegister   
 Dynamic Classes
Index -> Programming, Ruby -> Ruby Tutorials
View previous topic Printable versionDownload TopicRate TopicSubscribe to this topicPrivate MessagesRefresh page View next topic
Author Message
Cervantes




PostPosted: Mon Mar 20, 2006 8:24 pm   Post subject: Dynamic Classes

Classes in Ruby are dynamic. I say this for two reasons.

Re-opening Classes
First, we can declare a class, do something else, then reopen the class later. The most common example of this is reopening one of the standard library classes to add a method.
Ruby:
class Array
    def each_group(n)
        (length / n).times do |i|
            yield self[i * n .. (i + 1) * n - 1]
        end
    end
end

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].each_group(3) do |trio|
    p trio
end

Output wrote:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

The Creation of a Class
The second reason classes are dynamic is because the interpreter goes through the class much as it would your main program.
Ruby:
class Test
    puts "Hello from the Test Class!"
end

Output wrote:
Hello from the Test Class!

The Test Class is defined and the code within it executed. Notice that we did not have to create a new Test object to get the "Hello..." message. That happened upon defining the object - no more. We could write an entire program within a class, and it would run fine.

Wrap Your Program in a Class
We can wrap a standard program -- one that contains methods and input and output, for example -- inside a Class, and it will run fine. Our "standard program" can even involve Classes of its own. This means we would have a class within a class.
Ruby:
class MyProgram
    class Foo
        attr_reader :bar
        def initialize (bar)
            @bar = bar
        end
    end
    puts "Hello, from the MyProgram Class, where we have"
    puts "just defined the Foo class. Prepare yourselves,"
    puts "for we are just about to create a Foo object"
    puts "within the MyProgram class!\n"
   
    inside_foo = Foo.new(42)
    puts "The value of 'bar' within the 'inside_foo' object is: #{inside_foo.bar}"
   
    puts "\nSuccess! We have created a Foo object and accessed"
    puts "it, all as we would normally do, but from within"
    puts "the MyProgram Class."
end

puts "\nThat was some journey; but we've made it to the"
puts "outside world alive! Now that we're out here,"
puts "let's do a little testing.\n"
puts "First, let's try to create a Foo object from the outside"

outside_foo = MyProgram::Foo.new(4)
puts "The value of 'bar' within the 'outside_foo' object is: #{outside_foo.bar}"

puts "\nSuccess! Once again, we have created and"
puts "accessed a Foo object."

The output to this program is all detailed by the program itself. It works as expected. 42 is output as the value of bar for the inside_foo object, and 4 is output as the value of bar for the outside_foo object.

Living within "Object"
In the first few minutes of learning Ruby, you don't see any object orientation.
Ruby:
puts "Hello World"

But there's OOP just below the surface.
code:
irb(main):001:0> self
=> main
irb(main):002:0> self.class
=> Object

We're actually living within the Object class. It's as if we've got:
Ruby:
class Object
    puts "Hello World"
end

This brings new meaning to the phrase, "Everything in Ruby is an Object".

Eval
We can get more dynamic, though. We can use the power of eval and its many forms to do some awesome things. eval takes a string or block of code and interprets it. We can use instance_eval, class_eval, and module_eval, to evaluate code at different levels.
Ruby:
class Person
    def initialize(name_str)
        @names = name_str.split
       
        if @names.length == 2
            instance_eval {
                def first
                    @names.first
                end
                def last
                    @names.last
                end
            }
        end
    end
end

clapton = Person.new("Eric Clapton")
puts clapton.first
puts clapton.last

elvis = Person.new("Elvis")
puts elvis.first
puts elvis.last

output wrote:
Eric
Clapton
test2.rb:27: undefined method `first' for #<Person:0xb7d4f5ac @names=["Elvis"]> (NoMethodError)

For Eric Clapton, we gave the initialize method a string that contained two names, "Eric" and "Clapton". When we determined that this Person object has two names, we create methods first and last to get the first and last name of the Person. This is done dynamically by passing a block of code to instance_eval. However, Elvis Presley only gave his first name, so the first and last methods were never created.

The code you pass to eval can be dynamic, itself. Let's look at another example, this time using class_eval. Let's say I have a module that defines a bunch of methods that I need to use in a class. However, I can't use them quite as they are. I want to change them slightly, but all in the same way. What should we do?

For starters, let's make the assumption that I'm programming an IRC client in Ruby. The client needs to accept lines of input from the server, parse them, then display and log them, all nicely formatted. The format for the displayed and logged line will be the same, so we shouldn't have to rewrite the code to parse the string. Don't Repeat Yourself; DRY.

Let's create a module that will parse the input. It would define a lot of methods (one method for each IRC command), but we'll simplify this example to only show the PRIVMSG command.

Ruby:
module FromIRC
    def privmsg(irc_input)
        # Example irc_input: "Minsc PRIVMSG wtd Hey wtd!"
        # The above string is a simplified version
        #   of what the IRC servers use. It means,
        #   "Minsc" sends the "PRIVMSG" command with two
        #   arguments. The first is the receiver, "wtd".
        #   The second is the text, "Hey wtd!"
        words = irc_input.split
        "#{words.first}: #{words[3 .. -1].join(" ")}"
    end
end

class Channel
    include FromIRC
    def initialize
        # Open the log file for appending
        @log_file = File.open("log.txt", "a")
    end
    def log(text)
        @log_file.puts text
    end
    private :log
   
    # Create a slightly modified version of all
    #   the FromIRC methods.
    # Create them as instance methods, though we
    #   use class_eval to do this, because we're
    #   working at the class level (notice we're not
    #   within any instance methods, like in the last
    #   example).
    FromIRC.public_instance_methods.each do |method_str|
        class_eval "def #{method_str}(irc_input); log super(irc_input); end"
    end
end


Channel.new.privmsg("Minsc PRIVMSG wtd Hey wtd!")

This will write "Minsc: Hey wtd!" in the log file.

We could then use the same trick in the Display class, where we need to modify the methods just slightly to add the name of the server to the beginning of the string, and we need to 'puts' that new line. Now we've used one module to format a line for two classes and created all the methods we need in those classes. We have avoided repeating ourselves. Without the class_eval trick, we would have had to manually create one method for each IRC command for each of our two classes.
Sponsor
Sponsor
Sponsor
sponsor
Cervantes




PostPosted: Sun Apr 02, 2006 4:35 pm   Post subject: (No subject)

We've seen some pretty dynamic things happening, thus far. But there's more.

Renaming Methods
The names of methods can be changed using alias or alias_method. Let's look at a simple example of alias.

Ruby:

class Foo
    def bar
        puts "Barrrrry"
    end
    alias qux bar
end

foo = Foo.new
foo.qux
foo.bar

Output wrote:

Barrrrry
Barrrrry

Notice the use of alias: alias new_name old_name. This suggests that alias is a keyword. alias_method, on the other hand, is a method.

Ruby:

class Foo
    def bar
        puts "Barrrrry"
    end
    alias_method(:qux, :bar)
end

foo = Foo.new
foo.qux
foo.bar

Output wrote:

Barrrrry
Barrrrry


Aliasing is often used when a method needs to be slightly redefined, though it still needs to use the basis of the old method.
Ruby:

class String
   
    # Rename the 'inspect' method to 'old_inspect'
    alias old_inspect inspect
   
    # Overwrite the 'inspect' method with our definition
    def inspect
        "Value at ID #{self.object_id} is #{old_inspect}"
    end
end
foo = "The Wind Cries Mary"
puts foo.inspect

Output wrote:

Value at ID -605548906 is "The Wind Cries Mary"

Aliasing is also used when a method in a child class needs to be redefined, but the original version of the method needs to stick around. Let's look at an abstract example.
Ruby:

class Foo
    def baz
        puts "Barrry"
    end
end

class Bar < Foo
    alias foo_baz baz
    def baz
        super
        puts "From the Bar class"
    end
end

Bar.new.foo_baz
puts "----"
Bar.new.baz

Output wrote:

Barrry
----
Barrry
From the Bar class


Notice that neither alias nor alias_method remove the original method. To do that, we must use undef.

Removing Methods
We can dynamically create methods using class_eval and instance_eval, but what do we do with those methods if we don't want them later? If we dynamically created them, the condition which was true when they were created may no longer be true later. To remove a method, we can use the undef keyword. Let's look at a basic example.
Ruby:

class Foo
    def bar
        puts "Barrrrry"
    end
    undef bar
end

Foo.new.bar

Output wrote:

test.rb:8: undefined method `bar' for #<Foo:0xb7d18e58> (NoMethodError)

Remember that the code inside a class is executed line-by-line. The bar method is defined, then it is undefined. Calling the bar method on a Foo object gives us a NoMethodError.

Let's go back to our Person class, where we dynamically created two instance methods, first and last, if the name we were given had two names. Let's create an attr_writer of sorts for @name. However, we must undefine the first and last methods for the Person object if the new name does not have two names. To do this, we need to undef singleton methods. We cannot simply do undef first, last because that would try to undefine regular instance methods -- methods that exist for all Person objects. first and last are singleton methods -- they are instance methods that only exist for some Person objects. To undefine singleton methods, we must use instance_eval to undefine the methods in the context of the current instance.
Ruby:

class Person
    def initialize(name_str)
        @name = name_str.split
        if @name.length == 2
            instance_eval {
                def first() @name.first end
                def last() @name.last end
            }
        end
    end
    def name=(new_name)
        @name = new_name.split
        if @name.length != 2
            instance_eval { undef first, last }
        end
    end
end

musician = Person.new("Eric Clapton")
puts musician.first
puts musician.last
musician.name = "John Lennon"
puts musician.first
puts musician.last
musician.name = "Sting"
puts musician.first
puts musician.last

output wrote:

Eric
Clapton
John
Lennon
test.rb:26: undefined method `first' for #<Person:0xb7cc3390 @name=["Simon"]> (NoMethodError)

As you can see, the first and last methods no longer exist for our musician object after he has been renamed to "Sting". And so they shouldn't.

The ability to rename and remove methods is quite poweful and incredibly dynamic. Ruby's classes cannot be thought of as static definitions of objects. They can change and evolve. Keep this thought in the back of your mind as you're programming (not necessarily in Ruby) and look for places where a dynamic class would help to solve a problem.
Cervantes




PostPosted: Sat Apr 29, 2006 10:37 am   Post subject: (No subject)

Another way to remove methods

undef is one way of "removing" a method, but perhaps not the best. The Module class has a method called "remove_method" which actually removes the method. undef prevents the class from responding to its receiver. For example, assuming the Foo class already had a bar method, class Foo undef bar end prevents Foo objects from responding to calls to method bar. remove_method, on the other hand, does not act this way. It removes the method, which may or may not result the same NoMethodError being prodcued when that method is called.

But if we remove a method, then try to call it, how can that not produce a NoMethodError, you ask? Easy: Inheritance. If the parent class defines a method foo, then the child class overwrites foo, then using remove_method or unde will give different results. Using undef will produce or familiar NoMethodError. Using remove_method, however, will call the parent's version of foo. Here's an example:

Given the following base code:
Ruby:

class Parent
    def yar
        puts "YARR!! ~Dad"
    end
end

class Child < Parent
    def yar
        puts "Yar... ~Child"
    end
end

If the following code (using undef) is placed after the base code, we get a NoMethodError:
Ruby:

class Child
    undef yar
end
Child.new.yar

output wrote:
test.rb:16: undefined method `yar' for #<Child:0xb7d4bb74> (NoMethodError)

However, if the following (using remove_method) is placed after the base code, the Parent's version of yar is called:
Ruby:

class Child
    remove_method :yar
end
Child.new.yar

output wrote:
YARR!! ~Dad

remove_method is a method, not a keyword (which raises the question, why am I bolding it?). Thus, it is called differently from undef, which can be called like this: undef foo, bar, baz. remove_method does not accept the method name directly. Rather, it accepts it as a Symbol or a String: remove_method :bar, :baz.
Display posts from previous:   
   Index -> Programming, Ruby -> Ruby Tutorials
View previous topic Tell A FriendPrintable versionDownload TopicRate TopicSubscribe to this topicPrivate MessagesRefresh page View next topic

Page 1 of 1  [ 3 Posts ]
Jump to:   


Style:  
Search: