I'm a Ruby noobie, I admit it. Coming from a .NET background, Ruby has been a learning experience for sure, but I certainly feel lke I've learned something along the way. One aspect of the Rails framework I really like is the relaxed attitude to working with the database - I really rarely have to worry about it.
That said, I've been puzzling over the use of inheritance with Active Record. In .NET land and with NHibernate, I'd quite happily have this:
class AbstractUser class AdminUser : AbstractUser class StandardUser : AbstractUser
All the *User subtypes have their own extra properties, and the AbstractUser provides some common ones like email, password, username.
Ruby doesn't actually provide for abstract classes, so that's one issue. But another issue is the way in which ActiveRecord deals with inheritance. Only single table inheritance is supported via the user of a discriminator column. My issue with this is that there's no good way to support extra properties on a subclass - the properties would have to be added to the Users table and therefore without some trickery would be available to all User subtypes.
One possible solution for this is as follows:
class AbstractUser < ActiveRecord::Base end class AdminUser < AbstractUser has_one :admin_details end
In this case, we have another model - and therefore another table - which provides the AdminUser's extra properties. This can then be used as follows:
admin = AdminUser.new admin.admin_details.direct_line = '+44 567 7890'
This works fine, the values are off in a separate table and everything's separated out as I'd like. But the syntax is clunky. I don't really want to have to do admin.admin_details.direct_line, I want to do admin.direct_line. So I wrote a little extra method to do this:
class AdminUser < AbstractUser incorporates :admin_details end
All this does is set up a has_one as normal, but then provide extra methods for getting and setting all of the attributes of admin_details, meaning that admin.direct_line will work as expected. The code for incorporates has_one is up on github now - it's the minimum that was required to get this to work, so please feel free to fork it. I do wonder if I'm missing something that's already built into Rails, but if I am, it's damn hard to find.
This approach makes me pretty happy overall - in the scenario I've got right now, I never actually need a base User, which is why I'm referring to it as AbstractUser, and it would really be better if that could never be instantiated. But it's a minor niggle, and the whole setup plays well with Rails' polymorphic routing and suchlike, and works with Authlogic too. I can already see some improvements, so I hope someone forks the gist and I can learn something from Rubyists!