One precursor of me writing an article is if I keep forgetting how something's done, causing me to write a reference to look back on for later. This is one such article.

What's in Store for Today?

We'll be looking at the next type of decoration, which involves prepending a module in the call chain.

Table of Contents

<< Previous | Next >>

What Does Prepending Look Like?

Prepending is when we take a bit of code and we put it in front of the original:



Prepended -> Original -> Call Chain

It's different than include , which inserts after the original:



Original -> Included -> Call Chain

Included in the Mix

To demonstrate, let's make a talk function in a module, Talkable , and include it in a Lemur class:



module Talkable def talk ; "Hi there!" end end class Lemur include Talkable end Lemur . new . talk # => "Hi there!"

What happens if Lemur has its own talk method though?:



module Talkable def talk ; "Hi there!" end end class Lemur include Talkable def talk ; "I wasn't expecting you today" end end Lemur . new . talk # => "I wasn't expecting you today"

Because Lemur has a method for talk , it gets called first in the call chain.

Prepended Instead?

What if we switch include to prepend instead?



module Talkable def talk ; "Hi there!" end end class Lemur prepend Talkable def talk ; "I wasn't expecting you today" end end Lemur . new . talk # => "Hi there!"

Oh, if you're in pry or irb , remember that that include from earlier will still be there. Ruby's open classes can make for some fun debugging problems.

prepend puts Talkable#talk in front of Lemur#talk , causing it to get called first.

This has an interesting side effect as well: We can use super to refer to the original class:



module Talkable def talk ; "Hi there! #{ super } " end end class Lemur prepend Talkable def talk ; "I wasn't expecting you today" end end Lemur . new . talk # => "Hi there! I wasn't expecting you today"

Remember this one, because there are all types of fun implications there.

Extend the Conversation

Then what's extend ? It does neither of those two, it extend s a class with the modules methods, making them class methods instead.

Confused? Oh that's no problem whatsoever, I frequently end up switching between the two ( include and extend ) until my code works some times. I should probably write a full article on call chains some time later with picture references to remind me. Pictures make everything more fun.

Ah, right, prepend for decoration, right right. Let's get back to that.

Decorating with Prepend

Once again, we're going to need to know a few things, and venture down the metaprogramming rabbit-hole a few more meters.

Modules on the Fly

You've seen modules created like this:



module Talkable def talk ; "Hi there!" end end

In Ruby, we can define a module dynamically:



Module . new do def talk ; "Hi there!" end end

Like most expressions in Ruby, this is a value, which means we can do all types of fun things with it.

A Constant Experience

We could assign this to a variable, or if we really wanted to we could make a constant out of it:



mod = Module . new do def talk ; "Hi there!" end end Lemur . const_set ( "Talkable" , mod ) # => Lemur::Talkable

After that's done, we can check whether or not that constant exists:



Lemur . const_defined? ( "Talkable" ) # => true

Then we can get it:



Lemur . const_get ( "Talkable" ) # => Lemur::Talkable

Mind, you can't do this at top level (TIL, that's no fun...), but you can do it on any defined class like Object or Kernel even! (though that would be particularly naughty.)

Point being, these constants give a nice name to a potentially dynamically defined value, and allow us to do some fun things.

The Classy Metaprogramming Extraordinaire

What if we want to dynamically define a few methods? We remember define_method from some of the previous articles, but what if we wanted to define a method inside of a module?

Ruby lets us do that too with class_eval :



mod = Module . new phrases = { hello: "Why hello there!" , goodbye: "Fare thee well!" } mod . class_eval do phrases . each do | name , phrase | define_method ( name ) { phrase } end end mod . instance_methods => [ :hello , :goodbye ]

But this is a module! We should have used module_eval ! Well, we could, but they're the same thing:



[ 2 ] pry ( main ) > $ mod . class_eval From : vm_eval . c ( C Method ): Owner : Module Visibility : public Number of lines: 5 VALUE rb_mod_module_eval ( int argc , const VALUE * argv , VALUE mod ) { return specific_eval ( argc , argv , mod , mod ); } [ 3 ] pry ( main ) > $ mod . module_eval From : vm_eval . c ( C Method ): Owner : Module Visibility : public Number of lines: 5 VALUE rb_mod_module_eval ( int argc , const VALUE * argv , VALUE mod ) { return specific_eval ( argc , argv , mod , mod ); }

$ is a shortcut for showing the source of something in pry . I use it quite a bit. ? does the same but for its documentation.

Prepending at Last!

Now that we know all that, it's time to tie it all together in a grand metaprogramming bit of magic!

The fun part is that most of the techniques from the previous two sections are still very much relevant here. For this one though, I always want to know how long something took.

Let's make a prepended decorator to tell us just that! We'll call it Timeable :



module Timeable end

Inside of it we're going to need a method to wrap another, which you may remember from the Symbol method:



module Timeable def self . included ( klass ) klass . extend ( ClassMethods ) end module ClassMethods def measure ( method_name ) end end end

Now we're going to need to use that knowledge of constants to give ourselves a hook point to add things into. It'd be rather impolite to prepend several modules to a class, so we keep it in one place. This also helps with tracing things down later if we need to debug:



module Timeable def self . included ( klass ) klass . extend ( ClassMethods ) end module ClassMethods def measure ( method_name ) timing_module = if const_defined? ( "Timing" ) const_get ( "Timing" ) else const_set ( "Timing" , Module . new ). tap ( & method ( :prepend )) end p timing_module end end end class Lemur include Timeable measure "Something" end # => Lemur::Timing

We're using tap here as prepend returns back the constant that something was prepended to. Lemur , in this case, but we want the Timing constant instead. The p at the end is to prove that it works.

Either the constant exists, or we create it and prepend it to the class that includes Timeable

A Time and a Place

Let's implement this thing:



module Timeable def self . included ( klass ) klass . extend ( ClassMethods ) end module ClassMethods def measure ( method_name ) timing_module = if const_defined? ( "Timing" ) const_get ( "Timing" ) else const_set ( "Timing" , Module . new ). tap ( & method ( :prepend )) end timing_module . class_eval do define_method ( method_name ) do |* args , & fn | start_time = Time . now result = super ( * args , & fn ) puts "Time taken: #{ Time . now - start_time } " result end end end end end class Lemur include Timeable measure def super_cached_array_sample @super_cached_array_sample ||= ( 1 .. 100_000_000 ). to_a . sample ( 10 ) end end

Running that gets us this:



indigo = Lemur . new indigo . super_cached_array_sample # Time taken: 3.165698 # => [30125753, 40206899, 23039117, 35232027, 60498349, 60359828, 24456489, 58646248, 96152882, 69191217] indigo . super_cached_array_sample # Time taken: 2.0e-06 # => [30125753, 40206899, 23039117, 35232027, 60498349, 60359828, 24456489, 58646248, 96152882, 69191217]

If we take a look at the ancestors of our dear Lemur class, we find something fun:



Lemur . ancestors # => [Lemur::Timing, Lemur, Timeable, Object

The only thing we're doing here is using the time from before the original method runs and the time after, and using puts to log that out to the screen for us.

But What About Method Added?

We could, we certainly could at that. It's also more code, so I leave that as an exercise to the reader to combine these two techniques into something fun!

Learning Ruby is a process of learning several small things that help us come up with answers to bigger problems. Once you develop an intuition for what things go where with a veritable toolbelt you'll be ready for almost anything.

Do mind one of those tools is using a search engine and asking for help every now and then. I certainly still do.

Wrapping Up

Magic upon magic, and this has been quite the series to write. Who knows, I may even sneak another part into here somewhere, though the only remaining ideas I have are far darker magics involving TracePoint and I've already written a few articles on that.

I may well translate them over to dev.to and finish up the last few parts. Decoration is certainly magic, but TracePoint is straight up arcane level fun and shenanigans.

As with all magic, discretion is wise, but avoiding it entirely? Sometimes you need the chainsaw to trim a tree, and by golly we're going to get you a bright shiny magical metaphorical chainsaw to go trim the most epic of trees you can imagine.

Table of Contents