Sure, the inner workings of the system is as you describe, but the approach this article takes does make sense out of a developer standpoint, no? Sure "has_many" is just a method that the late-binding architecture of Ruby allows us to write, but this means that we can dream up our own DSLs and implement our DSL by doing what we do best - write methods ;) Which is a less general approach than the full-blown Lisp macros guns.
Spot on - they can certainly be used for similar goals (building powerful abstractions). But just as a horse and a bicycle can both be used to get places, but still be different, Ruby meta-programming and Lisp-style macros are very different, even if they are sometimes used to accomplish similar goals.
When you do meta-programming using Ruby, you're writing code that is executed at run-time (and can use runtime data). When you're writing Lisp macros, you're literally writing a code pre-processor: a bunch of little programs that walk over your code and expand it to other code before execution begins.
A great question is what the relative limitations are of both. I don't have a definitive answer, but I think this is one of the fundamental questions software engineering should attempt to answer - what abstractions can we build at compile-time, and what abstractions must be left for run-time?
Is that right for me as a developer?