How to create a class on the fly in Ruby
“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.
Related posts:
- Ruby 101: Make your class behave like a Ruby built-in I got re-acquianted with this scenario while working on the OpenAmplify gem – a wrapper for the OpenAmplify API. When you give the api a text like a blog comment,...
- Ruby 101: How to add methods to a Ruby class Let’s add a method that checks whether an Array has many elements. a = [1,2,3] a.many? # NoMethodError: undefined method `many?' Let’s fix this by adding a new method to...
- How to create Google-friendly URLs in Rails If you are building content-management systems or any applications with pages that you want to appear in a Google search, it is very important that you employ search engine optimization...
- Ruby 101: How to filter an Array using proc Over at the PhRUG, a Ruby developer community based in the Philippines, we conduct code review sessions via our mailing list. A code is posted and members share alternative implementations....
- Easy testing on Ruby OpenID consumer implementations This talk is organized by the The Vancouver Ruby/Rails/Merb Meetup Group. The presentation will include: OpenID background. what it is and why you would like to use it. OpenID consumer...
Thanks for the excellent example. From your example, is it possible to use Object.const_set(“Student”, Class.new)
and the student class define inside a string
=======================
student_string = ”
class Student
def name
@name
end
def name=(_name)
@name = _name
end
end”
=======================
How to load student_string as a class?
Steve
30 Mar 10 at 3:46 pm
@steve, you can use eval()
irb(main):002:0> eval(student_string)
=> nil
irb(main):006:0> klass = Object.const_get(‘Student’)
=> Student
irb(main):007:0> klass.new
=> #
irb(main):008:0> Student.new
=> #
Greg Moreno
30 Mar 10 at 4:45 pm