1

I am brand new to Ruby and even newer to DataMapper, or any ORM for that matter. I come from a Perl background and am not really an OOP type of developer. So I'm wandering in unknown territory here. Apologies for the long-winded question...

In this project, I have the concept of deviceclasses and devices which will be mapped under the deviceclasses. Deviceclasses need to be able to have child deviceclasses. The common root deviceclass names (in other words the root from which all deviceclasses come) are called "FOO" or "BAR" (in this example code) and each of those can have an arbitrary set of children devicesclasses. Finally, deviceclasses eventually contain devices.

So: Deviceclasses have many deviceclasses Deviceclasses have many devices A deviceclass has one deviceclass_name many devices belong to a deviceclass

So, IE:

FOO
    JOHNSHOUSE
         UPSTAIRS
             device1
             device2
         DOWNSTAIRS
             device1
             device2
    MANYHOUSES
        JOE
            GARAGE
                device1
                device2
        SUZY
            BEDROOM
                device1
                device2
                device3
        TIM
            LIVINGROOM
                device1
    ARBITRARY
        device1
        SOMEPLACE
            device1
            device2
BAR
    ENGLAND
        LONDON
            MYHOUSE
                BEDROOM
                    device1
                    device2
                    device3

And here's where I'm getting stuck...devices and deviceclasses must be able to be autonomously added to the DB and their associations will be performed later. So, I can't do

deviceclass = MyDB::Deviceclass.new
device      = MyDB::Device
deviceclass.device.new(blah)

My module, which contains the pertinent models for which I'm basing this set of questions...

Question 1 - Am I doing this right? Note that self.validate_root_deviceclasses method under Deviceclass. I have to have the n root deviceclasses in the DB before anything else so this method creates them. Unfortunately, the property update does not work. I would love some direction on that.

module DeviceDB
    ROOT_DEVICECLASSES %w{FOO BAR}

    class Deviceclass
        include DataMapper::Resource
        property :id, Serial
        property :hw_id, String,                :unique  => true
        property :root_deviceclass, Boolean,    :default => false
        property :parent_deviceclass_id, Integer
        property :deviceclass_name, String
        property :updated_at, DateTime
        property :created_at, DateTime

        has n, :devices,       :through => Resource
        has n, :deviceclasses, :through => Resource
        has 1, :deviceclass, self, {:through=>:deviceclasses, :via=>:parent_deviceclass_id}

        def self.validate_root_deviceclasses
            root_deviceclasses = all(:root_deviceclass => true)

            if root_deviceclasses.count > 0 
# get whats in the db now
                db      = Array.new(root_deviceclasses.map(&:deviceclass_name))
# match it against the global list (top of this file)
                missing = ROOT_DEVICECLASSES.map{|root| root unless db.grep(/#{root}/i)[0]}.compact

# if something's missing, add it.
                missing.each do |missing|
                    begin
                        create(:deviceclass_name => missing, :root_deviceclass => true).save
                    rescue DataMapper::SaveFailureError => e
                        @error = [e.resource.errors.map{|err| err}].join(', ')
                        return(false)
                    end 
                end 
            else
                begin
                    ROOT_DEVICECLASSES.each do |root|
                        create(:deviceclass_name => root, :root_deviceclass => true).save
                    end 
                rescue DataMapper::SaveFailureError => e
                    @error = [e.resource.errors.map{|err| err}].join(', ')
                    return(false)
                end 
            end 

            begin
                default = first(:deviceclass_name => 'PTS').id

                property :parent_deviceclass_id, Integer, :default => default # fail
                DataMapper.finalize                                           # fail
                return(self)
            rescue DataMapper::SaveFailureError => e
                @error = [e.resource.errors.map{|err| err}].join(', ')
            end

            return(true)
        end
    end

    class Device
        include DataMapper::Resource
        property :id, Serial
        property :deviceclass_id, Integer
        property :device_id, String, :unique => true
        property :devicename, String
        ... more properties...
        property :updated_at, DateTime
        property :created_at, DateTime

        belongs_to :deviceclass, :required => false
    end

    DataMapper.finalize
    DataMapper.auto_upgrade!

    Deviceclass.validate_root_deviceclasses
end

Question2: is there some magical way to associate the deviceclasses and devices or do I need to do it the tough way by grabbing an id of the device and associating it via update to an associated deviceclass?

Question3: Is there a way I can add a property to the model after the table has already been migrated which would effectively change the table by adding :default (see the fail case above). If not, is there any way I can obtain my default value during the creation of the model. Lambda comes to mind but that would only work if the table already exists and the ROOT_DEVICENAMES have already been added.

Jim
  • 1,499
  • 1
  • 24
  • 43

1 Answers1

1

For the first question about the schema and adding items autonomously you might consider using dm-is-tree for your Deviceclass it'll do a lot of the work for you. Below is an example of creating an item and 'later on' adding associated items.

require 'rubygems'
require 'data_mapper'
require 'dm-is-tree'

# setup
DataMapper::Logger.new($stdout, :debug)
DataMapper.setup(:default, 'sqlite::memory:')

# models
class Deviceclass
  include DataMapper::Resource

  property :id,          Serial
  property :hw_id,       String,   :unique  => true
  property :name,        String
  property :updated_at,  DateTime
  property :created_at,  DateTime

  is :tree, :order => :name
  has n, :devices
end

class Device
  include DataMapper::Resource

  property :id,               Serial
  property :device_class_id,  Integer
  property :name,             String
  property :updated_at,       DateTime
  property :created_at,       DateTime

  belongs_to :deviceclass, :required => false
end

# go!
DataMapper.finalize
DataMapper.auto_upgrade!

# make the root deviceclass
parent = Deviceclass.create(:name => "Root")

# later on make a child
child = Deviceclass.create(:name => "Child")
# and add it to the parent
parent.children << child

# again later, create some devices
d1 = Device.create(:name => "D1")
d2 = Device.create(:name => "D2")

# add them
parent.devices << d1
child.devices << d2

# get stuffs
puts parent.children
puts child.root
puts parent.devices
puts child.devices

I'm not sure it's a good idea to use validation to generate missing Deviceclasses. If the initial data is not constantly changing I'd run a seed script on startup. You can use something like dm-sweatshop to seed the db.

I think I need a few more details on #3 do you want a default name for a Deviceclass (you can add :default => 'foo') which you might know as you are using :default already! :)

kreek
  • 8,774
  • 8
  • 44
  • 69
  • Nice! I'll check into the dm-is-tree and dm-sweatshop modules. Thanks for those pointers! As for #3 what I'm basically trying to accomplish is inserting default records into a table for which the :default will be derived from one of those records for the property :parent_deviceclass_id. Any additional thoughts on that front? – Jim Mar 22 '13 at 17:19