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.
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! :)