Hacker News new | past | comments | ask | show | jobs | submit login
Metaprogramming in Ruby: It’s All About the Self (yehudakatz.com)
61 points by ivey on Nov 16, 2009 | hide | past | web | favorite | 17 comments



Everyone interested in this topic should watch this talk:

http://mtnwestrubyconf2008.confreaks.com/11farley.html

Ruby Internals - Patrick Farley


For whatever reason some people have obviously downmodded your comment. I just want to say, that was a really good presentation on Ruby internals. I watched the whole thing, and bookmarked it to go through again later. Thanks for the link.


Thanks for posting this. Along with the article posted here it clarified a lot of question I had.


It pains me that metaprogramming in Ruby is often treated as some mystic advanced voodoo topic, something only the adepts are free to enjoy.

It may seem weird to someone coming to Ruby from VB or Java, but a little time peeking under the hood is revealing, and it should be one of the first things explained to Ruby Nubies (instead of shoved off into chapter 23 or something in the typical Ruby book).


I often feel, when reading articles like this about programming, that they would be significantly enhanced by telling me why I should care about metaprogramming.

It is not obvious to me that there is any value in being able to declare a method on an object via metaprogramming versus just writing the method into the class. If you, like me, have to see obvious value to change your programming practices, let me provide you with a bit of motivation:

Metaprogramming allows you to cut down on boilerplate code, making your programs shorter, easier to write, easier to read, and easier to test. Also, it reduces the impact of changes.

For example, metaprogramming is used internally to provide a lot of the "magic" that people associate with Rails. You never have to define a find_by_email method in ActiveRecord because just putting an email column on the table causes that method to be automatically generated for you. That saves you perhaps three lines of code in the instant case, but when you start to compound that over several dozen columns on 10 different tables, you're talking about hundreds of lines of negacode.

Yeah, great for library writers, right? How do you use this in actual application code? Well, to pick one example from my application: a stupendous portion of the "business logic" involved in my app is creating print jobs. Print jobs have a grab bag of properties associated with them and, every time I implement a new feature, that means they typically get another property (or several). If I had implemented that via adding a new column every time I added a new feature, I'd have to add a new column to my table every week, and it would get progressively harder and harder to manage.

Instead, I have a column called options in the table which is just a JSON grab bag. My model class has a hash called default options, which has a bunch of keys representing virtual properties and a bunch of values for what they should take if the user hasn't specified anything. A quick little metaprogramming loop goes over the default options hash and adds getters and setters for those properties, so that they work transparently from the object. (For example, print_job.name = "foo" sets print_job[:options][:name] = "foo".) Another line of code sets the default values at initialization time.

This means that adding another property to my print jobs requires adding one entry to that hash, rather than writing a bunch of getters, setters, and dealing with migrating the 100,000 print jobs in the database which don't have a value for it yet. This is extraordinarily powerful in reducing the impact of changes.

For example, I recently added color support to my application. This required adding five properties to my print jobs. I just checked in SVN for how much of an impact this had on the printing class -- nine lines. The 35 lines of boilerplate getters/setters I didn't have to add contained exactly no typos, required no test cases, and caused no bugs. The data migration I didn't write caused no downtime. The special cases that don't exist for jobs without color information caused no subtle bugs two weeks later when they fell out of the cache and then got regenerated.

[Edited to add: It occurs to me that I have a contrasting example, too. I do Big Freaking Java Enterprise Web Apps at the day job.

Recently, we had to add the JSON grab bag approach to our internal framework, because it would allow us to customize what data we associate with e.g. students on a per-university basis without requiring code changes, separate schema, and a redeployment.

This took a lot of blood, sweat, and tears from our best engineer, myself, and one contractor -- probably about 2.5 man months, total. We did it with 100% plain ol' Java, rather than metaprogramming.

It will save us literally hundreds of thousands of dollars, so that isn't a terrible tradeoff, but if I had been using Ruby, I could have done it by myself in two to three days.]


well_said <<QUOTE

Metaprogramming allows you to cut down on boilerplate code, making your programs shorter, easier to write, easier to read, and easier to test. Also, it reduces the impact of changes.

QUOTE

...and your JSON grab bag technique would make a nice blog post.


Alright. Be forewarned, it will be about 15 lines of code and 2,000 words of explanation on why you want to do it that way. Check HN or my blog (in profile) tomorrow morning US time.


That's exactly why it makes a good blog post.


" A quick little metaprogramming loop goes over the default options hash and adds getters and setters for those properties, so that they work transparently from the object."

Where in your app does this loop exist? Does most Rails metaprogramming happen in models or controllers?

Great post. Very informative.


Cards on the table: I think Rails developers, as a community, spend too much time on "What is the right way to do this" questions when they could spend 40% of the time on "Does this way make sense?" and 60% on implementing more features for paying customers.

That being said: the fact that this example both cares about how the data is structured internally and discusses the "business logic" of my domain both strongly encourage putting the associated logic in the model.

You can certainly do metaprogramming in controllers, though I have no particular opinion on what the mix is for projects other than mine. An example where I could see using metaprogramming in a controller is having live AJAX-y update of fields in a form. (Why put that in the controller? Because it involves how you are structuring access from the application. And, more pragmatically, because adding methods to a controller gets you routes for free and adding methods to a model does not.)

Rather than defining 10 different actions which are each strikingly similar, just iterate over what fields are in the model and add a controller method for each. You'll get your routes for free, so all you have to do is hook it all together in the view, which you can do with metaprogramming-enabled helpers.


I think Rails developers, as a community, spend too much time on "What is the right way to do this" questions when they could spend 40% of the time on "Does this way make sense?" and 60% on implementing more features for paying customers.

I do not self-identify as a Rails Programmer, so I can't speak for what they do and why. But I will say that taking the time to decide on the "right" way to do X is a win if X ever comes up again.

That is very nearly the definition of "right" as opposed to "convenient" or "pragmatic:" What way makes sense now and in the future, what way is a general pattern we can apply when the same situation and forces arise?

We often think that this won't come up in this app again, so why invest the extra energy? But in a blogging environment, when you share and discuss your idea of the right way with others, the community benefits from your work. Likewise, you benefit from others taking the time to navel-gaze over the right way to do stuff you haven't encountered yet.

Summary of my suggestion: Investing extra energy in thinking about the "right" way to do things is beneficial in a broadly collaborative and highly communicative environment like the blogosphere.


I think the whole notion of "you should be spending more time doing X" is kind of pointless. People do whatever they think is fun, especially for open source projects. Maybe you should be spending that time saving whales instead!


What's more, if there's one group of programmers who should be worried about doing things The Right Way, it's library writers.


I think he's talking about people using Rails worrying too much, not the people writing the Rails framework.

Just as an aside, I totally agree that we spend too much time worrying about this sort of thing. I'm definitely guilty of this myself. I think it's probably born out of the heavy use of "convention over configuration" in Rails; whenever I try to do something, I always think "Am I following convention here?". My instinct now usually tells me "probably not, but just get it done and worry about that later".


Hey I was just curious about your experience, not really any "right" way to do anything. I'm an intermedaite rails developer at best and still wrapping my head around Ruby metaprogramming. Context was my goal.

You can throw your cards on my table any time, though. I don't mind.


I do the same in Rails with "serialize :attr, Hash". It works really well.


I highly recommend reading the Metaprogramming Ruby book from pragprog for people who wish to learn more.

Read the entire book in a couple of days and it really opened my eyes to new ways of doing things.

http://www.pragprog.com/titles/ppmetr/metaprogramming-ruby




Registration is open for Startup School 2019. Classes start July 22nd.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: