back

Cool Trick for ActiveSupport StringInquirer in Rails 2.2, Part II

This is part II of a quick look at StringInquirer. Start with part I

So, in part I we saw that being able to have multiple methods ending in ? for strings must be possible for Rails.env because of some hokus pokus rather than because of the StringInquirer class, as it’s not possible using only ActiveSupport::StringInquirer.new("foo").

Why…?

First let’s look at how ActiveSupport implements StringInquirer, then we’ll look at how Rails.env.x allows us to use multiple methods ending in ? that can return true. I’m going to assume you have some understand how things like method_missing work.

So, let’s take a sample use of StringInquirer then step through what happens in the code.

So, when I first call Momoro.new.food I instantiate a new instance of the StringInquirer class which is subclass of string, so you’re getting back “basil” instead of whatever it’s set to.

This means that Momoro.new.food will return a StringInquirer, rather than the value itself. So if I set the food to “bread” calling momoro.food would return a stringinquirer rather than bread!

So, moving into the actual method ( plus a few printfs to make things clear):

So, we can see that, like many methods in rails, method_missing for StringInquirer first checks to make sure that it’s being called in the right way or at all, in this case by checking to see if the last char is a question mark.
(Note that array[-1,1] gives you the last element in the array.)

After that, it checks whether self (in this case the StringInquirer instance) is equal to the whatever we’re asking about. So, if I had instead passed in m.food.apple?, the method name would be :apple, and self would != :apple.to_s

Finally, if we didn’t call a method with a question mark, the code calls super which just steps up the class “ladder” and looks to see if String implements the method, etc.

So, this isn’t exactly the behavior I’m looking for, in that what I want is to be able to call m.food.anything?

So, it should be pretty obvious by now that we can do:

Which lets us actually do:

However, note that if we call m.food right after creating a new Momoro, we’ll get “TypeError: can’t convert nil into String,” because we’re trying to call String.new(nil), which doesn’t work.

So, instead let’s do:

This way we avoid raising an exception, because Momoro.new.food will now just return "". We could use the ternary operator or something to actually return nil if the attribute were nil, but I don’t want to make github pissed at me for making so many pointless gists.

This is probably pretty slow, but we now have the basic functionality that we wanted.

How does Rails do it?

So, how does RoR do Rails.env.production? and Rails.env.development? Here’s the code, which looks like it was changed to it’s current form here.

So, rails memoizes the new StringInquirer object, which is initialized only once with the value of RAILS_ENV.

December 09, 2008