“So what if Ruby is dynamic?”
This is often the reaction I get whenever I tell friends that Ruby allows you to fiddle with your program at runtime; followed by an emotional discussion on dynamic vs. static way of programming. Instead of adding more fuel to the fire, I’ll just show how Ruby’s dynamic nature makes programming more fun.
In this post, we will learn how to create a functioning class at runtime.
The static way of starting with an object-oriented language is to define a class. In Ruby, to create an instance of this class we would use the ‘new’ method. If you are new to Ruby, I am using ‘irb’ to write the program and I suggest you start it now so you could follow me.
>> class Table >> end >> t = Table.new >> t.class => Table
Every time you use the ‘class’ keyword, you are adding a new item in your program’s namespace. Imagine a namespace as just a directory of names you can use in your program. So if you have the 2 classes defined, you have 2 names available in your program. (Technically, you will have more than 2 but for simplicity let’s leave that topic for another post, shall we?) For example:
>> class Book >> end >> class Shelf >> end >> b = Book.new => #<Book:0x5c6a48> >> s = Shelf.new => #<Shelf:0x5c286c> >> t = Student.new => NameError: uninitialized constant Student
If you try to use Student, your program barks because it can’t find ‘Student’ in your program’s namespace.
In our first and second example, the way to make Student available to our program is to use the keyword ‘class’. Now what if we don’t know the name of the class yet – the only time we would know it is when the program is already running.
Now, let’s make our “dynamic” journey more interesting by creating a class based on some user input. For example, the user would give you a name (i.e. as a string), and you’ll create a class based on that name. Let’s see how this is done:
>> class_name = "Student" # assume this is inputted by the user >> klass = Object.const_set(class_name, Class.new) >> instance = klass.new => #<Student:0x5e08a8> >> instance.class => Student
Voila! We have created a Student out of thin air! For those coming from a static way of doing things, it is alright to take a break; a cup of coffee also helps🙂
Ok, you’re back.
>> Object.const_set("Student", Class.new)
This line adds the name “Student” in your program’s namespace and make sure it behaves like a normal class as if we defined it using the ‘class’ keyword. Thus, if we try to use Student this time, our program does not complain.
>> t = Student.new => #<Student:0x5b1828>
Now, we have two ways of creating a student:
>> s1 = Student.new => #<Student:0x5cba0c> >> s2 = klass.new => #<Student:0x5c34b0>
But wait! There’s more. It wouldn’t be fun if our class does nothing. So let’s add some attributes to it. Let’s go back to our static ways. During your early days with with Ruby, you probably define attributes this way:
>> class Student >> def name >> @name >> end >> >> def name=(_name) >> @name = _name >> end >> end
But now you know ‘attr_accessor’, which is a shortcut for creating get/set method pairs like we have in the previous example.
>> class Student >> attr_accessor :name >> end >> s = Student.new => #<Student:0x5d3720> >> s.name = 'greg' => "greg" >> s.name => "greg"
But how can we add the attributes in a class that we have not defined yet? Simple, we open its brain and add our attributes – at runtime.
>> attrs = ['name', 'age'] >> klass.class_eval do >> attr_accessor *attrs >> end >> >> s = klass.new => #<Student:0x5d2f78> >> s.name = 'greg' => "greg" >> s.age = 24 => 24
The magic here is done by the ‘class_eval’ method. It executes all the code between the ‘do’ and ‘end’ as if you have written the class. Using ‘class_eval’, you can also add methods to a class at runtime.
All these stuffs so far are cool but what the heck would you use it for? You can’t certainly use it to pick up girls in a bar. Otherwise, a lot of Ruby programmers would not spend their time writing blog posts on metaprogramming🙂
If you like this post, please buy me a book for my birthday.