Ruby on Steroids: The Magic of MetaProgramming – Fellowship of Spells
In part 1 of this journey, you were introduced to anatomy of magic(metaprogramming) and you saw some of the spells you can cast with the magic of metaprogramming. In this post, I will be showing you other forms of spells you can cast.
Fellowship of Spells
For us to become better magicians, we need to learn more spells. Let’s see how far we can go in the fellowship
of spells.
1. Â define_method
define_method
is used to define a method dynamically at runtime. In part 1 of this series, we saw how we can define methods dynamically using eval, class_eval or instance eval. The major problem with defining your method with this means is that it is prone to errors since your methods are defined using strings. A good time to use define_method
against using explicitly defined methods is in a situation where you have to create a series of methods all of which have the same basic structure.
1 2 3 4 5 6 7 8 9 10 11 12 |
class Router [:get, :post, :put, :patch, :delete].each do |method_name| define_method(method_name) do "This is a #{method_name} method" end end end router = Router.new router.get #=> "This is a get method" router.post #=> "This is a post method" router.delete #=> "This is a delete method" |
2. Class.new
We all know that everything in ruby is an object, even classes. Ruby class is an instance of Class
. That means every time we are defining a new class, we are basically calling Class.new
behind the scene. Let’s see how this works.
1 2 3 4 5 6 |
class Person attr_accessor :name def book_reading "Elixir Cookbook" end end |
Creating a class this way is a syntactic sugar for creating it this other way.
1 2 3 4 5 6 |
Person = Class.new do attr_accessor :name def book_reading "Elixir Cookbook" end end |
Ever wondered why you call Person.new
but define Person.initialize
? It’s pretty simple. Here:
1 2 3 4 5 6 7 8 9 |
VALUE rb_class_new_instance(int argc, VALUE *argv, VALUE klass) { VALUE obj; obj = rb_obj_alloc(klass); rb_obj_call_init(obj, argc, argv); return obj; } |
Obviously! This is the source for Person.new
. You might not read C, but it’s pretty simple: it first allocates the space for the object using rb_obj_alloc
, and then calls initialize
using rb_obj_call_init
.
3. const_get & const_set
const_get
is used to retrieve a constant with same name as the string or symbol passed as the argument to the const_get
method. It can be used to load ruby classes from strings. It is the method behind ActiveSupport
constantize method. const_set
on the other hand can be used to create new constants or to update existing constants.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Book = "Eloquent Ruby" Module.const_get(:Book) #=> "Eloquent Ruby" Module.const_set(:Book, "Ruby Science") #=> "Ruby Science" Module.const_get(:Book) #=> "Ruby Science" class Person def name "Sword Master" end end person_class = Module.const_get(:Person) #=> Person p = person_class.new p.name #=> "Sword Master" Module.const_set(:Trainer, Class.new { attr_accessor :stack }) trainer_class = Module.const_get(:Trainern) #=> Trainer trainer = trainer_class.new trainer.stack = "Ruby" trainer.stack #=> "Ruby" |
As you can see in the last example above, we can do a lot of neat things by combining const_set
and Class.new
. We can dynamically create different classes at runtime.
4. const_missing
We just saw how we can get a constant dynamically using const_get
. However what happens if we try to get a constant that do not exist? Well, const_missing
method is called as a method of last resort, which by default throws a NameError
. We can override this const_missing
method with our own implementation.
1 2 3 4 5 6 7 8 9 10 11 |
class Module def const_missing(constant) "Constant #{constant} is really missing" end end class House end Module.const_get(:House) #=> House Module.const_get(:Trainer) #=> "Constant Trainer is really missing" |
5. binding
Instances of the Binding class (binding objects) capture the environment bindings (variables, methods, and self) at any point of a Ruby program, so the bindings can be reused later, when the scope has changed. It is this binding objects
that powers binding.pry
. Infact pry gem just adds a pry
method to the binding object. Let’s see how binding works.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class Person def initialize(n) @name = n end def issues family = "No Money" relationship = "Complex" friends = "No Friends" binding end def get_binding binding end end aboki = Person.new("Aboki") aboki_binding = aboki.get_binding aboki_issue_binding = aboki.issues T eval("@name", aboki_binding) #=> "Aboki" eval("@name", aboki_issue_binding) #=> "Aboki" eval("family", aboki_issue_binding) #=> "No Money" eval("relationship", aboki_issue_binding) #=> "Complex" aboki_issue_binding.local_variable_get(:friends) #=> "No Friends" eval("family", aboki_binding) #=> undefined local variable or method `family' |
Basically binding.pry
effectively opens up a pry session with the current binding object. Check out this link for a deeper understanding of binding
and/or binding.pry
.
Conclusion
We often hear that a journey of a thousand miles starts with a step. That is also true in the world of magic. You are already making those steps and before long, you will become a full fledged magician. Till the next time we journey together, keep your finger crossed and practice your new spells.
Remember, without practice, you cannot become a master magician.
- Ruby on Steroids(DSLs): The Powerful Spell Called DSL - March 14, 2016
- Ruby on Steroids: The Magic of MetaProgramming – Method Spells - March 12, 2016
- Ruby on Steroids: The Magic of MetaProgramming – Fellowship of Spells - February 28, 2016
- Ruby on Steroids: The Magic of MetaProgramming – An Unexpected Journey - February 27, 2016
- How to Delegate Like a Boss - February 24, 2016
- Better Routing – Part 4 - January 28, 2016
- Autoloading and Utility Methods – Part 3 - January 11, 2016
- Set up a Basic MVC Framework – Part 2 - December 22, 2015
- Rack Deep Dive – Part 1 - December 17, 2015