A feature I recently added to my Ruby feed fetching and parsing library, Feedzirra, is a call to sanitize the html in feed entries to protect from cross site scripting attacks. Adding the functionality was simple because of Dryopteris, a library written by Mike Dalessio and Bryan Helmkamp. The only thing left to decide was what I wanted to API to look like.
I settled on an odd pattern that I haven't used before and I offer it up here for your review. It was inspired by how the named_scope functionality in ActiveRecord works. I have no idea what you call this, so I refer to it in the title of this post as a proxy object. (here's a gist of my proxy object example for nicer reading)
# Let's say we have a feed entry object entry = feed.entries.first # now I could provide access to sanitization a few ways sanitized_content = Feedzirra::Feed.sanitize(entry.content) # but that's verbose and ugly, so maybe I could do this sanitized_content = entry.sanitized_content # that looks better, but I still didn't like it for some reason. # I could also do this santitized_content = entry.sanitized(:title) # and define the sanitized method in entry like so def sanitized(method) Dryopteris.sanitize(send(method)) end # However, what I really wanted was something like this sanitized_content = entry.sanitized.content # I think this reads a little better # Here's the code inside of entry that makes this possible def sanitized dispatcher = Class.new do def initialize(entry) @entry = entry end def method_missing(method, *args) Dryopteris.sanitize(@entry.send(method)) end end dispatcher.new(self) end # So in the sanitized method I'm creating a new proxy class # on the fly. I'm redefining method_missing to send # it to the method straight back to the entry. # The last is an initialization of this proxy object that # passes the entry itself in so it can have methods called against # it later. So now I can also do entry.sanitized.author entry.sanitized.titleI'm still debating whether this is a good idea or just completely stupid. I think the API looks pretty clean, but it may just be confusing. Would I be better off just going with one of the first three examples? Would it be better to do something completely different?