Wednesday, May 03, 2006

ruby adaption

i ran into a case last night where i think i'm trying to write python code in ruby.

only problem is, i like writing python code ;)

here's the gist of it:

lets say, for argument, that i have a standard User object. this user object is the most simple base class for, example, two other kinds of users, Admins and Customers. What I would do in python would be simply subclass Users and create the other two. now i admit, this gets a little tricky any time you're using an ORM. i can generally think of how i'd do it in sqlalchemy, just because i've used it more than the others, but still, it's not so "drop in and let it work" simple.

for ruby, and the rails orm, the only design pattern for subclassing requires a single-table design where all fields that will ever be needed by a subclass are contained in that same table. and while my "keep all subclass implementation independent!" hackles rise on hearing that kind of thing, in realistic situations, this is probably not a bad design choice. if the class is never going to be used outside of your own app, and esp in ruby/rails if the class is only to serve as the model component in MVC, subclassing it is only ever going to relate to your own data anyway.

but still, it bothers me.

my first thought toward a solution was having something like Users and CustomerDetails, and creating a non-table-tied mixin class Customer. doing something like would create an object that had both User and CustomerDetail as parents, theoretically having the attributes of both. but that doesn't really work out with the ORM.

i'd heard ruby doesn't support multiple inheritance. i found this link by maurice codik who has an interesting solution for the problem, and it kinda works for what i want, but not entirely.

it seems like subclassing or multiple-inheriting from two model-dependent classes just doesn't really work, which makes sense. the orm doesn't have anything mapped for those attributes, so attempting a save will, at best, only save for the first parent that answers that method.

so, we override the save/update/edit/delete behaviors. this though makes me nervous. activerecord is a complex beast, and my ruby isn't good enough yet for me to feel comfortable knowing that i've overridden the behavior in all necessary places. i may revisit this, but not immediately.

also, it's not really a solution that i can reuse. every time i want to subclass something, i need to make sure that the two tables that the original classes are tied to don't have overlapping namespaces. and i'd need to catch every possible update, spin over both classes in the @parents array, check parent.kind_of?() and find out which one to append the new info to, then save them both.

it's possible, but it's not clean or pretty.

so my next thought was implementing a kind of simple adaption, one object to another. basically spin over all attributes and methods provided by one object, and append the relevant ones to a new instantiation of the kind of object i want to adapt to, and return the new style object back out.

this works, to a point. but the ORM again doesn't have any mapping to the new object, so i either maintain in the new object a list of parent-to-attribute mappings so that on all updates/creates/deletes etc i save back to the right kind of object, or... i don't know on the or. i couldn't think of any other way to do it.

but again, it's a poor solution. every time i want to save the hybrid object, i have to first look at, in this case, the User object, and pull up the CustomerDetail object with a find on the user id, and if one doesn't exist, create it, add it to the user object, and THEN translate my values back into it via some iteration over attributes.

again, none of this is impossible, but i don't know enough about ruby to really abstract it. so DRY goes right out the window.



Post a Comment

<< Home