devxlogo

Ruby Comes to the .NET Platform

Ruby Comes to the .NET Platform

icrosoft’s IronRuby project brings a powerful and fun dynamic language to the Windows platform. The Ruby programming language is a modern, object-oriented scripting language, with a syntax inspired by languages such as Perl and Smalltalk. It was conceived by Yukihiro Matsumoto (aka “Matz”). In his words, Matz wanted a language that was “more powerful than Perl and more object-oriented than Python” (read more from Matz). The language is designed to feel natural; something Matz calls the “principle of least surprise.” Version 1.0 of the language was released in 1996.

For several years, Ruby existed as a little-known scripting language that excelled in the tasks for which it was originally designed: manipulating data and environment with the least amount of effort. When I first started toying with it a few years ago, I was looking for something to replace batch files for automating some of my more common administrative tasks.

Ruby’s popularity really began to pick up when a small software company from Chicago, Illinois, named 37signals released a Web application framework named Rails. This new framework combined proven patterns like Model-View-Controller and ActiveRecord with new ideas like “convention over configuration.” The end result is a framework that achieves a lot with almost no code.

RubyCLR and IronRuby
In early 2006, John Lam went public with a project named RubyCLR, which worked as a bridge between Ruby and .NET. It allowed the user to have direct access to the richness of the .NET platform from Ruby, and even expose Ruby objects to the CLR. This project was very ambitious, but it wasn’t about putting Ruby on .NET so much as it was about letting the two worlds talk to each other. You still needed the Ruby runtime to be installed on your machine.

The RubyCLR project was a tremendous first step toward understanding how to get Ruby and .NET to live together in harmony. John’s work did not go unnoticed, and at the end of 2006, he announced on his weblog that he would be joining Microsoft’s newly formed Dynamic Language Runtime (DLR) team. Just a few months before John’s announcement, Microsoft released version 1.0 of IronPython, a new implementation of the Python language on the .NET Framework. The Dynamic Language Runtime work takes the work done on IronPython and builds a runtime on top of the .NET Framework that allows dynamic languages to be brought into .NET with much less effort.

John and his team announced IronRuby at the MIX conference in 2007. Perhaps more surprising than the revelation of the IronRuby project itself was that it would be the first truly open source .NET language from Microsoft. Not only would the source code be available, but outside contributions from the community are also accepted.

IronRuby is still a work in progress. Although drops have occasionally been available, usually in the context of another project (such as the recently released Silverlight 2.0 Beta 2), those following the project are keeping up with the source tree and participating in the mailing lists.

Why Learn Ruby?
One of my favorite books, The Pragmatic Programmer: From Journeyman to Master (Andrew Hunt and David Thomas, Addison-Wesley Professional), encourages you to learn a new language every year. For me, the language that most changed my world-view when I learned it was Ruby.

Ruby is a fully object-oriented language in the Smalltalk sense. That means everything you interact with in the system is an object, including direct values like numbers. Even classes, which form the templates from which new object instances are created, are themselves objects in the system.

Because Ruby is a dynamic language, you’ll find that types become much less important. When a method takes an object as a parameter, you don’t need to specify what type that object needs to be. In fact, there is no compiler, so you might not discover until runtime that the object you passed to a method wasn’t suitable for the requirements of that method.

If you’re like I was a few years ago, you might find this concept a little unsettling. Aren’t compilers here to provide help to you, so you can know these things as soon as possible before they cause runtime errors? The answer is, of course, yes: compilers do provide better feedback, sooner. So why should you throw them away and use a language like Ruby?

As it turns out, compilers are a very seductive form of straitjacket. The ability of compilers to tell you about mismatched types necessarily causes a form of type-rigidity. When you write a method, you may wish to say “objects here must be able to do foo and bar” and then invent an interface called IFooBar. That seems like a neat solution for describing the requirements?but it fails you when you want to use someone else’s classes (especially those from the framework) that were written before IFooBar existed.

Author’s Note: Although IronRuby isn’t quite ready for mainstream use, you can use the standard version of Ruby for your explorations. If you want to follow along with the examples, you can download the Ruby installer for Windows.

Ruby by Example
The best way to learn a language like Ruby is by exploring it interactively. Many dynamic languages have interactive prompts known as a Read-Execute-Print Loop (REPL). The REPL program for Ruby is called irb (which standards for “interactive Ruby”).

When you execute the irb program, you’ll see an irb prompt:

   C:UsersBrad> irb   irb(main):001:0>

You type commands into the irb prompt and the Ruby interpreter evaluates them, with the results printed to your screen. REPLs like irb are an excellent way to learn a language: one statement at a time.

As an introduction to irb, tell Ruby to add two numbers. At the irb prompt, type 5 + 2 and press Enter:

   irb(main):001:0> 5 + 2    => 7

The irb(main):001:0> part is the prompt that irb gave you. When you typed 5 + 2 and pressed Enter, irb printed the result to you as => 7. The => is your hint that irb is showing you the result of the evaluation.

If Ruby believes you haven’t finished your expression, it will allow you to continue. For example, if you were to type 5 + 2 + and press enter, Ruby realizes that you’re still missing part of the expression and lets you continue typing on the next line:

   irb(main):002:0> 5 + 2 +   irb(main):003:0* 13   => 20

The second prompt line ends with “*” instead of “>” to let you know that you’re still finishing a previous statement.

Editor’s Note: This article was first published in the September/October 2008 issue of CoDe Magazine, and is reprinted here by permission.

Basic Types
A programming language wouldn’t really be worth much if it couldn’t manipulate numbers. Simple arithmetic is no trouble for Ruby:

   irb(main):004:0> 3 + 4   => 7   irb(main):005:0> 3 * 4   => 12   irb(main):006:0> 3 - 4   => -1   irb(main):007:0> 3 / 4   => 0   irb(main):008:0> 3.0 / 4.0   => 0.75   irb(main):009:0> 0xF   => 15   irb(main):010:0> 0x3 * 0xA   => 30

As you can see, Ruby has support for integer and floating point types, and even accepts the common format for hexadecimal integers, although the result of 0x3 * 0xA was printed in decimal (30 rather than 0x1E).

As with .NET, numbers are actually objects, so you can call methods on them:

   irb(main):011:0> 14.to_s   => "14"

Don’t try that with C++.

To to_s method converts an object into a string, so calling 14.to_s returns the string “14.” As with .NET’s ToString() method, the to_s method is actually a method on Object, so you can convert everything in Ruby to a string.

Strings
Ruby’s strings have the full complement of operations that you might expect:

   irb(main):012:0> "hello" + "there"   => "hellothere"   irb(main):013:0> "Reader".length   => 6   irb(main):014:0> "Reader".reverse   => "redaeR"   irb(main):015:0> "reader".capitalize   => "Reader"   irb(main):016:0> "Reader".include?("foo")   => false   irb(main):017:0> "Reader".include?("ade")   => true   irb(main):018:0> "  Reader  ".strip   => "Reader"   irb(main):019:0> "Reader".gsub("e", "f")   => "Rfadfr"   irb(main):020:0> "Reader".delete("ea")   => "Rdr"   irb(main):021:0> "a" < "b"   => true

Several string operations are available that you’re probably not familiar with. For example, the following code tests whether a string lies between two other strings, alphabetically:

   irb(main):022:0> "Bob".between? "Adam", "Chris"   => true

The multiplication operator causes a string to repeat a specified number of times:

   irb(main):023:0> "hi" * 5   => "hihihihihi"

The crypt method provides a salted one-way hash of the given string, which is useful for storing sensitive data like passwords:

   irb(main):024:0> "Reader".crypt("ab")   => "abofgDjq6JNJo"

Characters
Ruby doesn’t have a built-in character type. It represents characters as numeric values. You can represent character constants with the ? syntax. You can use the chr method to convert a number into the string equivalent of its character:

   irb(main):025:0> "Reader"[2]   => 97   irb(main):026:0> ?a   => 97   irb(main):027:0> 97.chr   => "a"

Assignment
It’s not really helpful to just perform operations unless you can store the results for later use:

   irb(main):028:0> x = 42   => 42

Strings have a special syntax that allows embedded evaluation. This evaluation isn’t limited to just simple variable replacement, either: it’s a full evaluation:

   irb(main):029:0> "The answer is #{x}!"   => "The answer is 42!"   irb(main):030:0> "The answer is #{6 * 7}!"   => "The answer is 42!"

You can avoid the evaluation by surrounding your string with single quotes instead of double quotes:

   irb(main):031:0> 'The answer is #{x}!'   => "The answer is #{x}!"

Arrays
Arrays in Ruby work much like the ArrayList class from .NET 1.0. They are variable-sized arrays that can store any type of data, indexed starting with 0:

   irb(main):032:0> a = ["hello", 42, "world"]   => ["hello", 42, "world"]   irb(main):033:0> a << 5.0 * 7.5   => ["hello", 42, "world", 37.5]   irb(main):034:0> a[0]   => "hello"   irb(main):035:0> a[6] = 'hi' * 2   => "hihi"   irb(main):036:0> a   => ["hello", 42, "world", 37.5, nil, nil, "hihi"]   irb(main):037:0> a[99]   => nil

The preceding code shows how to use the push operator (<<) to add items to the end of the array, and the index operator ([]) to both get and set values. When you add an item past the end of the array, Ruby fills in the “holes” in the array with nil values. When you attempt to access values beyond the end of the array, Ruby returns nil rather than throwing an exception.

You can slice off part of the array by using a ranged index. You can also combine this with the ability to use negative indices to access the array from the back rather than the front (where -1 is the last item, -2 is the second to last item, etc.). Although you can’t use reverse ranges to get reversed slices, you can use a forward range, and then call the reverse method afterward:

   irb(main):038:0> a[-1]   => "hihi"   irb(main):039:0> a[1..3]   => [42, "world", 37.5]   irb(main):040:0> a[2..-2]   => ["world", 37.5, nil, nil]   irb(main):041:0> a[-4..-1]   => [37.5, nil, nil, "hihi"]   irb(main):042:0> a[-1..-4]          # Won't work   => []   irb(main):043:0> a[-4..-1].reverse  # Works fine   => ["hihi", nil, nil, 37.5]

Like strings, you’ll find several unique and useful methods available to arrays:

   irb(main):044:0> a   => ["hello", 42, "world", 37.5, nil, nil, "hihi"]   irb(main):045:0> a.compact   => ["hello", 42, "world", 37.5, "hihi"]   irb(main):046:0> a.join   => "hello42world37.5hihi"   irb(main):047:0> [10, 75, 6, 29].sort   => [6, 10, 29, 75]   irb(main):048:0> [[1, 2, 3], [4, 5, 6]]   => [[1, 2, 3], [4, 5, 6]]   irb(main):049:0> [[1, 2, 3], [4, 5, 6]].flatten   => [1, 2, 3, 4, 5, 6]

Hashes
The final core data structure in Ruby is the Hash, similar to .NET 1.0’s Hashtable. It is an associative array, where the indices (keys) can be any kind of value, and the data they point to (values) can also be any kind of data. In practice, most Hashes use symbols for keys (see the following section).

You declare hashes using the {} syntax, and you declare initialization values in “key => value” form. You can use the index operators to both get and set values in the hash:

   irb(main):050:0> h = {:foo=>'bar', :baz=>'biff'}   => {:foo=>"bar", :baz=>"biff"}   irb(main):051:0> h[:foo]   => "bar"   irb(main):052:0> h[:unknown]   => nil   irb(main):053:0> h[:baz] = "new"   => "new"   => {:foo=>"bar", :baz=>"new"}   irb(main):054:0> h.entries   => [[:foo, "bar"], [:baz, "new"]]

Variables
Variable (and method) names in Ruby start with a lowercase letter, and can contain letters, numbers, and underscores.

Local variables have no prefix. Instance variables are prefixed with @. Global variables are prefixed with $.

You do not need to declare variables before you use them. Uninitialized variables have the value nil.

There are several pre-defined variables:

  • nil is an object that represents nothing (comparable to null in .NET, except that nil is an instance of the NilClass class).
  • true and false are instances of the TrueClass and FalseClass.
  • self, when used from a method, is a pointer to the object instance from which the method was called. When it is used from within a class, it refers to an instance of the class object itself.
  • __FILE__ and __LINE__ return the currently executing file and the line number in that file.

Symbols
Ruby has a special kind of string known as a Symbol. Because strings can be changed in Ruby, using them as hash keys can be slow at best and unpredictable at worst.

The names of symbols follow the same rules as variables, except that they start with a colon (:). You can’t change the value of a symbol, and two symbols with the same name have the same identity, which makes them excellent hash keys; instead of comparing the value of a variable length string, a lookup requires only comparisons against integer values.

Classes
Everything in Ruby is an object, and objects are all instances of classes. To discover what class something is, call the class method on it:

   5.class   => Fixnum   (2 ** 96).class   => Bignum   7.5.class   => Float   (1..10).class   => Range   "foo".class   => String   /^foo[a-e]$/.class   => Regexp   :foo.class   => Symbol   [].class   => Array   {}.class   => Hash

Blocks and Closures
Although there were things like event handlers in .NET 1.x, you were forced to define full methods to attach to events when you wanted them handled. This led to the creation of a lot of methods that really only existed because the framework demanded it.

.NET 2.0 introduced the concept of anonymous delegates. They function in much the same way as blocks do in Ruby:

   irb(main):001:0> h = {:foo=>'bar', :hi=>'there'}   => {:foo=>"bar", :hi=>"there"}   irb(main):002:0> h.each_key {|k| puts k}   foo   hi   => {:foo=>"bar", :hi=>"there"}   irb(main):003:0> h.each {|k,v| puts "#{k}: #{v}"}   foo: bar   hi: there   => {:foo=>"bar", :hi=>"there"}

As you can see, the syntax for blocks in Ruby is quite terse: Use curly braces to open and close the block, and use the |x,y| syntax to designate the variables passed to the block.

Blocks in Ruby also act like closures, just as anonymous delegates do in .NET 2.0. This means they have access to values in their enclosing scope, even after that scope has exited. Here’s a simple closure that multiplies together several values:

   irb(main):004:0> n = [5, 6, 10]   => [5, 6, 10]   irb(main):005:0> t = 1   => 1   irb(main):006:0> n.each { |i| t *= i }   => [5, 6, 10]   irb(main):007:0> t   => 300

You can even store references to blocks to be used later:

   irb(main):008:0> t = 1   => 1   irb(main):009:0> f = lambda { |i| t *= i }   => #   irb(main):010:0> n.each &f   => [5, 6, 10]   irb(main):011:0> t   => 300

Methods
Method definitions in Ruby are simpler than in .NET, because types are not specified:

   irb(main):001:0> def greet(name)   irb(main):002:1>   puts "Hello, #{name}!"   irb(main):003:1> end   => nil   irb(main):004:0> greet "Reader"   Hello, Reader!   => nil   irb(main):005:0> greet 42   Hello, 42!   => nil

Ruby performs something called “Duck Typing”: if it walks like a duck and quacks like a duck, it must be a duck. You don’t ask it “Are you a duck?”?you just assume it’s a duck and call quack on it. Even if you were expecting a duck, anything that can quack is allowed to join in the party.

Note that the greet method definition didn’t set any limits on the types of objects that can be passed in, because it just prints them?and everything in Ruby supports printing, by virtue of the to_s method on Object, which will convert the object into a string. The end result is that you can greet anything.

Here’s another example:

   irb(main):006:0> def print_len(item)   irb(main):007:1>   puts "Len = #{item.length}"   irb(main):008:1> end   => nil   irb(main):009:0> print_len "Reader"   Len = 6   => nil   irb(main):010:0> print_len [1, 4, 9]   Len = 3   => nil   irb(main):011:0> print_len 42   NoMethodError: undefined method 'length' for   42:Fixnum           from (irb):7:in 'print_len'           from (irb):11

The print_len method now does more than before: it calls the length method on the object passed to it. So the requirement for something to be passed to print_len is that it has a length method that you can call with zero parameters. Passing a string or an array works because they have an appropriate length method; but passing a number doesn’t work, because they don’t have a length method.

To write a method like this in .NET, you’d probably need to invent an IHaveLength interface and use that as your parameter type. For classes invented before you created your interface, you’re out of luck, or you end up creating type converters. The whole thing spirals out of control quickly as the strong typing system gets in your way.

On the other hand, at least if you had IHaveLength, you understand what the requirements of the method are. Since types act as a form of documentation, you need an alternative in dynamic languages, which means you will be more reliant on things like written documentation and unit tests to help discern how some class or method is meant to be used.

Ruby supports default argument values:

   irb(main):012:0> def repeat(val, times = 5)   irb(main):013:1>   val.to_s * times   irb(main):014:1> end   => nil   irb(main):015:0> repeat "hi"   => "hihihihihi"   irb(main):016:0> repeat "hi", 3   => "hihihi"   irb(main):017:0> repeat 10, 3   => "101010"

Notice that there’s no return before val.to_s * times. Methods in Ruby always return the result of the last evaluation, unless explicitly told to return a value, so the return keyword is just seven extra characters you don’t need to type.

Ruby supports variable arguments:

   irb(main):018:0> def add(*values)   irb(main):019:1>   result = 0   irb(main):020:1>   values.each {|x| result += x}   irb(main):021:1>   result   irb(main):022:1> end   => nil   irb(main):023:0> add 1, 2, 3, 4, 5   => 15

Ruby packages up the variable arguments into an array. You can then access the values that are passed and (in this case) add them together.

Method and Variable Naming Conventions
Methods in Ruby start with a lowercase letter, and can contain letters, numbers, and the underscore.

Method names that change the underlying object end with an exclamation point (!). For example, the upcase method on strings returns the uppercase version of the string, but leaves the original string alone. In contrast the upcase! method actually changes the underlying string.

Methods that answer questions (returning Boolean values) have names ending in a question mark (?).

Classes
Classes are templates from which new object instances are created. For example, to put the greet method from above into a class you could write:

   irb(main):001:0> class Manners   irb(main):002:1>   def greet(name)   irb(main):003:2>     puts "Hello, #{name}!"   irb(main):004:2>   end   irb(main):005:1> end   => nil   irb(main):006:0> m = Manners.new   => #   irb(main):007:0> m.greet "Reader"   Hello, Reader!   => nil

The preceding code creates a new class named Manners, and adds the greet method to it. Finally, it creates a new instance of Manners, and uses it to greet the reader.

You should think of classes as being a living template for objects in Ruby. Unlike classes in .NET, which are defined at compilation time, you can extend classes in Ruby at any time, and when you’ve extended them, even existing instances of that class immediately gain the new behavior. Notice what happens when you attempt to say farewell:

   irb(main):008:0> m.farewell "Reader"   NoMethodError: undefined method 'farewell' for   #           from (irb):8

When you tried to call farewell, the system told you it didn’t know what it was. Now extend the Manners class and give it the ability to say goodbye:

   irb(main):009:0> class Manners   irb(main):010:1>   def farewell(name)   irb(main):011:2>     puts "Goodbye, #{name}!"   irb(main):012:2>   end   irb(main):013:1> end   => nil   irb(main):014:0> m.farewell "Reader"   Goodbye, Reader!   => nil

After extending the Manners class, your existing instance of it can now bid the reader farewell.

The Manners class has two methods, and both take your name. You should probably re-write it so you can pass the name when you create it. Ruby calls the initialize method with all the arguments you pass to new. Here’s an updated version of the Manners class:

   irb(main):001:0> class Manners   irb(main):002:1>   def initialize(name)   irb(main):003:2>     @name = name   irb(main):004:2>   end   irb(main):005:1>   def greet   irb(main):006:2>     puts "Hello, #{@name}!"   irb(main):007:2>   end   irb(main):008:1>   def farewell   irb(main):009:2>     puts "Goodbye, #{@name}!"   irb(main):010:2>   end   irb(main):011:1> end   => nil   irb(main):012:0> m = Manners.new "Reader"   => #   irb(main):013:0> m.greet   Hello, Reader!   => nil   irb(main):014:0> m.farewell   Goodbye, Reader!   => nil

Notice that the class stores the name in an instance variable named @name. Also note that inspecting the instance (the result, printed after line 12) includes the values of all the instance variables.

Class extensibility isn’t limited to just the classes you define. You can also extend the built-in classes in Ruby:

   irb(main):001:0> class Array   irb(main):002:1>   def print_tr   irb(main):003:2>     puts ""   irb(main):004:2>     each { |item|   irb(main):005:3*       puts "  #{item}"   irb(main):006:3>     }   irb(main):007:2>     puts ""   irb(main):008:2>   end   irb(main):009:1> end   => nil   Irb(main):010:0> ["hello","world!"].print_tr        hello     world!      => nil

Rails adds many extensions to the built-in types that provide a fluent interface. For example, you can write code such as 5.days.from_now and get back a time zone-aware date that is, well, five days from now.

The methods you’ve defined so far have all been instance methods; that is, they are methods that are available only on instances of a class. Ruby has static methods (sometimes called class methods) as well. In fact, you’ve already been calling a static method, without even thinking about it: new.

You can add new static methods to any existing class:

   irb(main):001:0> def String.concat(s1, s2)   irb(main):002:1>   s1 + ' ' + s2   irb(main):003:1> end   => nil   irb(main):004:0> String.concat 'hi', 'bye'   => "hi bye"

You can also define them in the context of defining or extending the class using the “self” syntax:

   irb(main):001:0> class String   irb(main):002:1>   def self.concat(s1, s2)   irb(main):003:2>     s1 + ' ' + s2   irb(main):004:2>   end   irb(main):005:1> end   => nil   irb(main):006:0> String.concat 'hi', 'bye'   => "hi bye"

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   => #   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   => "#"   irb(main):004:0> o.send "inspect"   => "#"

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   => #   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")   => #   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.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist