views:

187

answers:

1

Hi, so I'm setting up some models and they are based off of 2 abstract base classes (or rather they used to be classes). After running into a lot of trouble with Datamapper's handling of STI for my use case, which appears to be an open bug on their lighthouse page, I decided instead to just do modules to define all the properties to keep my models DRY. Unfortunately, I'm having a scoping issue, and what complicates matters worse is that I have to use 2 levels of inheritance. Here's my code:

module Part
  def self.included(receiver)
    receiver.class_eval do
      include DataMapper::Resource
      property :id,       Serial
      #other junk
    end
  end
end

module HardDrive
  def self.included(receiver)
    receiver.class_eval do
      include Part
      property :kind,          Enum[:magnetic, :flash]
      #buncha crap here
    end
  end
end

class Fujitsu
  include HardDrive
  property :rev, String
end

The error I get is:

uninitialized constant HardDrive::Enum (NameError)
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.4/lib/active_support/dependencies.rb:80:in `const_missing'
from ./app/models/hard_drive.rb:6:in `included'
from ./app/models/hard_drive.rb:4:in `class_eval'
from ./app/models/hard_drive.rb:4:in `included'
from ./app/models/hard_drives/fujitsu.rb:2:in `include'
from ./app/models/hard_drives/fujitsu.rb:2

I'm at a loss here. Anyone know of how I could solve this or better yet, a smarter way I could do this?

+1  A: 

It seems to me that Enum is defined under the DataMapper modules and the HardDrive scope does not resolve it. (Want to know why ?)

Just put DataMapper::Enum instead of Enum and it should work.

In a more general discussion, are you sure you really need these abstractions ? One drawback I see in your code is that you won't be able to query your database for parts and harddrives because the logic is stored in ruby modules instead of in the database.

Update (after comment from author)

The general answer is: forget about STI. While ORM are nice to have, the best part of them is SQL backend abstraction. While they give you the impression that you can have a persisten object model, the abstractions often leak and STI is a good example. I won't go in large details here but you can find resources online. Best is that you stay close enough to SQL modelling best practices, like one-one, one-many and many-many relationsships.

Here is an updated version. I didn't test it and the method names are probably wrong, but you will get the idea:

class Part
  property :serial_number
  has_one Manufacturer
end

class HardDisk
  property :technology
  property :platters
  property :model
  #...
  is_one Part
end

class Manufacturer
  property :name #Fujitsu, ...
  property :website
  #...
  has_many HardDisk, [:trough=>Part]
end
zimbatm
I would only want to use Part or HardDrive for finders, so I could do Part.all and then render views based on that, but that's not such a big loss.I've tried using inheritance but as far as I can tell, I can't use ruby inheritance and have it use different tables for the subclasses. I initially designed this with STI as I said but there's a known bug in DM on their lighthouse page that prevents that.What strategy would you use?
Technocrat