Skip to main content

Mobomo webinars-now on demand! | learn more.

As a new developer to Ruby you might wonder how certain methods seem to be magically available without being strictly defined. Rails's dynamic finders (e.g. find_by_name) are one example of this kind of magic. It's very simple to implement magic such as this in Ruby, but it's also easy to implement things in a way that doesn't entirely mesh with standard Ruby object expectations.

Your Friend method_missing

The way that many magic methods are implemented is by overriding method_missing. This special method in Ruby is automatically called by the interpreter whenever a method is called that cannot be found. The default behavior of method_missing is to raise a NoMethodError letting the user know that the method that was called does not exist. However, by overriding this behavior we can allow the user to call methods that aren't strictly defined but rather programatically determined at runtime. Let's look at a simple example:

     class Nullifier       def method_missing(*args)         nil       end     end      nullifier = Nullifier.new     nullifier.some_method     # => nil     nullifier.foo(:bar, :baz) # => nil 

Here we simply told method_missing to immediately return nil, regardless of the method name or arguments passed. This essentially means that, for this class, any method call that is not defined on Object (the default superclass for new classes) will return nil.

While this example is certainly interesting, it doesn't necessarily give us more use in the real world. Let's take another example that actually does something useful. Let's make a hash that allows us to access its keys by making method calls:

     class SuperHash  'def'     h.something_else # => NoMethodError 

This behavior gives us something pretty simple yet powerful: we have manipulated the foundation of the class to give us runtime methods. There's a problem, though: using method_missing alone is only half the story.

Quack Check With respond_to?

In Ruby, you can call respond_to? with a symbol method name on any object and it should tell you whether or not that method exists on the object in question. This is part of what makes Ruby's duck-typing work so well. So in our example, we also want to be able to know if a method is there using respond_to?. So let's add a new override for the respond_to? method of our example above:

     class SuperHash < Hash       def respond_to?(symbol, include_private=false)         return true if key?(symbol.to_s)         super       end     end 

Well, that was easy enough. Now our SuperHash will return hash keys based on method_missing and even tell you if the method is there with respond_to?. But there's still one more thing we can do to clean things up a bit: notice how we have repeated functionality in that we check key? in both methods? Now that we have a respond_to? we can use that as a guard for method_missing to make it more confident:

     class SuperHash < Hash       def method_missing(method_name, *args)         return super unless respond_to?(method_name)         self[method_name].to_s       end     end 

Wait, that can't be right, can it? Can we just assume that we can call the key like that? Of course! We already know that no existing method was called if method_missing is activated. That means that if respond_to? is true but no existing method was called, there must be a key in our hash that caused respond_to? to return true. Therefore we can confidently assume that the key exists and simply return it, removing the conditional and cleaning up the method_missing substantially.

Now that you know how method_missing and respond_to? can work together to add functionality to an object at runtime, you have a powerful new tool in your metaprogramming arsenal. Enjoy it!

Categories
Author

No matter what level of developer you are, at some point you will have the underrated joy of inheriting a legacy software project. And since we're fortunate enough to work in such a fast-paced community and ecosystem, "legacy" really encapsulates any piece of software more than a month or two old. Often though, we don't have time to appreciate how our ancestors used to write Ruby back in the days of Rails 2.3, or even (gasp) 2.1 — we need to get right to work. It's at this point that the nefarious Jabberwocky method can rear its ugly head.

The Jabberwock in the Room

When we first adopt a project, some of the language may seem like gibberish. Let's say we encounter this snippet of code in some controller:

@sword = VorpalSword.new @sword.snicker_snack! 

Looking at this code, we might expect to open up the VorpalSword model and find:

def snicker_snack!    Jabberwock.destroy_all end 

Unfortunately, in this all-too-contrived example, the VorpalSword class simply doesn't have that method!

The Jaws That Bite, the Claws That Catch!

Stuff and nonsense! Now we'll have to dig through and see if we can find a way to uncover this method. Let's start doing some sleuthing...

  • Maybe there's a method_missing in the VorpalSword class

  • Does VorpalSword inherit from anything?

  • Hmm...does it include or extend any modules?

  • It's probably just a plugin. Let's check the vendor/ folder...

  • Okay, maybe not. How about something under the lib/ folder...

  • Is "snicker_snack!" a gem?? Who would name a gem "snicker_snack!"

  • Did someone overwrite method_missing in ActiveRecord::Base?!

  • That's it, I'm switching to Scala!

Final Uffish Thoughts

Abstraction is a fantastic technique - we love to keep our software DRY and happy, but occasionally it can lead to code obfuscation. So before you go galumphing back to your project, take a few minutes and follow these suggestions to keep your code readable, and avoid those Jabberwocky methods:

  • Using modules is a self-documenting way to extend the functionality of your classes. If you do decide to use a shared library to cover some functionality, try to use modules rather than re-opening classes.

  • Be very careful when you use metaprogramming techniques like method_missing, or dynamic code creation. Writing a lot of crafty methods using a few lines of Ruby can be lots of fun, but sometimes a little bit of duplication can improve the readability of your code tenfold.

  • Lastly, documentation and tidy code is important. Try to organize your methods, and definitely drop a comment into the class if you know a shared library will be mutating it down the road.

Be sure to keep these tips in mind, and you'll find whoever inherits the code after you will chortle in his joy.

Categories
Author

I’ve been working a lot with seed data in my applications lately, and it’s obviously a problem that can be pretty aggravating to deal with. I ultimately liked the db-populate approach the best with the addition of the ActiveRecord::Base.create_or_update method. My one problem with it is that it’s based entirely on fixed ids for the fixtures, which is a pain to deal with when loading up your attributes from arrays. Here’s an example of what I was doing:

i = 1  { "admin"     => ["Administrator", 1000],    "member"    => ["Member", 1],    "moderator" => ["Moderator", 100],   "disabled"  => ["Disabled User", -1] }.each_pair do |key, val|   Role.create_or_update(:id => i, :key => key, :name => val[0], :value => val[1])   i += 1 end

I really don’t like having to put the i in there to increment up. Not only is it messier code, but it would be dangerous if I wanted to move around the roles. I also don’t want to have to have an id explicitly in each entry since I really don’t care what the id is. So I thought I would hack up a better solution for this create_or_update scenario and this is what I came up with:

class << ActiveRecord::Base   def create_or_update(options = {})     self.create_or_update_by(:id, options)   end      def create_or_update_by(field, options = {})     find_value = options.delete(field)     record = find(:first, :conditions => {field => find_value}) || self.new     record.send field.to_s + "=", find_value     record.attributes = options     record.save!     record   end      def method_missing_with_create_or_update(method_name, *args)     if match = method_name.to_s.match(/create_or_update_by_([a-z0-9_]+)/)       field = match[1].to_sym       create_or_update_by(field,*args)     else       method_missing_without_create_or_update(method_name, *args)     end   end      alias_method_chain :method_missing, :create_or_update end

Basically, this allows me to call create_or_update with an arbitrary attribute as my “finder” by calling Model.create_or_update_by(:field, ...). To give it a little taste of syntactic sugar, I threw in a method missing to allow you to name a field in the method call itself. So now the code I wrote before can become this:

{ "admin"     => ["Administrator", 1000],    "member"    => ["Member", 1],    "moderator" => ["Moderator", 100],   "disabled"  => ["Disabled User", -1] }.each_pair do |key, val|   Role.create_or_update_by_key(:key => key, :name => val[0], :value => val[1]) end

This is much cleaner and prettier to look at, and also makes sure that it is keying off of the value that I really care about. This new create_or_update combined with db-populate creates the most powerful seed data solution I’ve yet come across.

Categories
Author
1
Subscribe to Metaprogramming