Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Ruby Comes to the .NET Platform : Page 5

Find out why .NET programmers may want to learn and use Ruby, and discover the core syntax of the language.


advertisement
Reflection
Reflection is the process of discovering information about objects at run time. You can discover the methods available for a class by calling the methods method. The base class for everything in Ruby is Object, so here's a look at some of the methods available to every object in Ruby:

irb(main):001:0> o = Object.new => #<Object:0x3f8feb4> irb(main):002:0> o.methods => ["inspect", "taguri", "clone", "public_methods" , "taguri=", "display", "instance_variable_defined ?", "equal?", "freeze", "methods", "respond_to?", ...many more methods listed...

The result of calling methods is an array of strings containing each method name available on that object. You can think of a class much like a Hash; calling methods is like getting the keys to that hash table.

Knowing the methods wouldn't be interesting unless you could do something with them, of course. To call the method, you call the send method. The following operations are equivalent:

irb(main):003:0> o.inspect => "#<Object:0x3f8feb4>" irb(main):004:0> o.send "inspect" => "#<Object:0x3f8feb4>"

Ruby gives you an opportunity to handle unknown methods by defining the method_missing method on your class:



irb(main):139:0> class Object irb(main):140:1> def method_missing(*args) irb(main):142:2> puts args irb(main):143:2> end irb(main):144:1> end => nil irb(main):145:0> o.foobar 1, 2, 3 foobar 1 2 3 => nil

As you can see, the arguments passed to method_missing include the requested method as well as all the arguments passed to that method. A better definition would probably be:

def method_missing(method, *args)

Meta-Programming
Although Ruby does not have properties, you can use the fact that method invocation does not (usually) require parenthesis to simulate properties. You also need to leverage the fact that Ruby treats methods that end with = specially, making them act like setters.

You might define a person class as such:

irb(main):001:0> class Person irb(main):002:1> def age irb(main):003:2> @age irb(main):004:2> end irb(main):005:1> def age=(value) irb(main):006:2> @age = value irb(main):007:2> end irb(main):008:1> end => nil

Now you can use instances of Person, and treat age like a property of the person:

irb(main):009:0> p = Person.new => #<Person:0x89c5678> irb(main):010:0> p.age => nil irb(main):011:0> p.age = 42 => 42 irb(main):012:0> p.age => 42

If you wanted age to default to something other than nil, you could set it in an initialize method.

This code feels very boilerplate. If this were a language like C#, you'd probably be tempted to use things like snippets in Visual Studio or even static code generation to automatically generate the reader and writer of a property.

With Ruby, though, you can use meta-programming to create these things with just a small amount of effort. Ideally, you'd like to be able to write something like:

class Person prop :age end

You'll define a class (static) method on Object so that it's available to use when you're defining your class. You'll also use a method you haven't seen yet, the class_eval method:

irb(main):001:0> class Object irb(main):002:1> def self.prop *names irb(main):003:2> names.each { |name| irb(main):004:3* self.class_eval " irb(main):005:3" def #{name} irb(main):006:3" @#{name} irb(main):007:3" end" irb(main):008:3> self.class_eval " irb(main):009:3" def #{name}=(value) irb(main):010:3" @#{name} = value irb(main):011:3" end" irb(main):012:3> } irb(main):013:2> nil irb(main):014:2> end irb(main):015:1> end => nil

The class_eval method as used above is what ends up creating the methods. It evaluates the string just as if you'd done that in the context of the class itself. So you can write your methods just as if you'd been writing them inside the class all along.

Each name passed to the prop method adds two methods to the new class: a getter and a setter. The end result string replaces #{name} with the name that you passed to the prop.

Now you can use prop in your class definition:

irb(main):016:0> class Person irb(main):017:1> prop :age, :name irb(main):018:1> irb(main):019:1* def initialize(age, name) irb(main):020:2> @age = age irb(main):021:2> @name = name irb(main):022:2> end irb(main):023:1> end => nil irb(main):024:0> p = Person.new(36, "Brad") => #<Person:0x89c0768 @age=36, @name="Brad"> irb(main):025:0> p.age => 36 irb(main):026:0> p.name => "Brad"

With these kinds of facilities at your disposal, you can quickly create classes at a much higher level, using these kinds of meta-programming tricks to do much of the work for you, without needing to rely on editor snippets or compiler-time code generation.

Next Steps
This article really just scratched the surface of the facilities available in Ruby. Learning Ruby today will help you prepare for when Ruby is available on .NET and in Silverlight. Having such a powerful dynamic language will be a welcome addition to your programmer's tool belt, but more importantly, it will help you start to think about problems and solutions in a new way.

To continue your Ruby education, I strongly recommend Programming Ruby: The Pragmatic Programmer's Guide by David Thomas and Andrew Hunt (Addison-Wesley Professional), sometimes called "The Pickaxe" for its cover picture.



Brad Wilson is a software developer on the ASP.NET team and has worked at Microsoft more than three years. He has been a professional developer for more than 15 years. He is one of the co-creators of xUnit.net, a test-driven development framework for .NET 2.0.
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap