The last time I posted about this particular gem, my spam increased a thousand-fold and my blog became forever tied to the search term. You'd think I would have learned my lesson. But alas, kind readers, I have not. I present to you the end of a very long and frustrating day (relax Seth, the awesome prose is completely on me – no charge ;-) ).
#
# See: http://erector.rubyforge.org/userguide.html (Heading: 3. To the
# constructor of an Erector::InlineWidget) Contrary to what you'd normally
# expect with closures in Ruby, we don't have access to methods in the
# object that we call (note that we're still using v0.5.1 here - the new
# call uses {WidgetName}.new do {some block of code} end):
#
# render(widget) do
# {some block of code}
# end
#
# from. Within {some block of code}, "self" is *not* the same thing it is
# outside of the block. Inside the block, "self" refers to the widget.
# Although this is documented in the Erector user guide (and is probably
# easily caught *while* developing a feature), I don't think it is at all
# obvious when trying to debug an error that occurs when you accidently
# try to access a variable in the outer object from within this block. At
# least not unless you've been bitten by it. But by that time, you will have
# (if you share my fate) been chasing the bug for several hours, pulling your
# hair out, and muttering all sorts of nasty words. What I'm hoping here is
# that you instead see this error message, scratch your head for a few minutes,
# maybe mutter one or two nasty words, and then quickly track down the comment
# you're reading now. If this has indeed happened, and this is waaaaayyyyyyy in
# the future (it's only 2010 right now, and we're excited about the new iPhone 4,
# which probably seems very quaint to you already), please send an email to
# david.s.ackerman[-at-]gmail.com letting me know that my time of pain and great
# suffering was not spent in vain, and we can both hope I've still got a good
# enough memory to know what you're talking about!
module Erector
class Widget
def method_missing_with_local_variable_check(sym, *args, &block)
begin
method_missing_without_local_variable_check(sym, *args, &block)
rescue => error
# Only print out one error message per method
@local_variable_errors = [] if !@local_variable_errors
if(!@local_variable_errors.include?(sym))
RAILS_DEFAULT_LOGGER.error "Couldn't find :#{sym} within the widget."
RAILS_DEFAULT_LOGGER.error "Check that you're not referencing a local variable within a render(widget) block.
RAILS_DEFAULT_LOGGER.error "Search for this error message in the codebase to find further explanation."
RAILS_DEFAULT_LOGGER.error "-----------------backtrace begin---------------------"
error.backtrace.each { |line| RAILS_DEFAULT_LOGGER.error line }
RAILS_DEFAULT_LOGGER.error "------------------backtrace end----------------------"
@local_variable_errors << sym
end
end
end
alias_method_chain :method_missing, :local_variable_check
end
end
This reminds me of a coding maxim that I hear quite often these days. I can't remember the exact words, but the gist of it goes something like this: However clever you are in the code, you (or someone else) will have to be even more clever when fixing any bugs associated with it. Of course, lack of cleverness can always be made up for by beating your head against the proverbial wall (yay me!).
I'm not trying to slag the folks who cooked up this little bit of cleverness. It probably seemed quite obvious at the time, and it makes inline widget inclusion (its raison d'etre) a snap. And to their credit, they did document it. The problem is that the error I was getting (a missing method error) was such that even the best google-fu wouldn't bring me to the right documentation. And Erector is not like ActiveRecord, etc. in that any Rails developer will automatically be familiar with it. So I think it would have been lazy of me, after having this particular eccentricity bite me, to not try to prevent it from biting someone else. Someone who's been using Erector since day one will probably think I'm pretty dumb, but there will also be plenty of smart folks out there who have no clue what I'm even talking about. It's the nature of the code beast, and I've seen many programmers smarter than me who've been tripped up by things others thought were obvious to worry about feeling dumb.
Ruby is a language that allows all of us to be very clever, but can we also protect ourselves from the perils of being clever? Tests are one thing, but I'm not sure there's a test that could have prevented someone from making the error that the above code is meant to flag – unless you wanted to parse through your entire code base. I'm basically trying to do what an assertion on a block might do (if you could do such a thing – as far as I understand it, the whole point of a block is that you can put a ton of random stuff in it). And this makes me think that a good test suite does not completely replace the benefits of assertions in the code.
Perhaps it boils down this: tests are all about what you know should happen (even when you test negations, you're still testing something based on your knowledge of how it should operate – i.e. it's not a Black Swan), while assertions (and assertion-like code) are all about what you know shouldn't happen (the Black Swans).
Anyway, all that aside, the really important thing is that it might give someone a chuckle or two in a few years!