I posted a message about this earlier, but I've refined my approach quite a
bit, so I thought I'd share the new version.
The intent is to be able to have sparse properties in a model. If I'm tracking,
say, contents and characteristics of rooms in a house, I might have (it's a
lousy way to represent a room, but it's just an example):
table rooms (
id sequence,
size integer,
roomtype varchar,
carpeted boolean
)
If the room's a bedroom, then I want to have "# of beds" as a property, but if
it's a kitchen, then "# of sinks" might matter. So there's an associated table
to store that kind of info:
table roomproperties (
room_id integer,
key varchar,
value varchar,
primary key (room_id,key),
foreign key (room_id) references rooms(id)
)
What's different from just the usual Sequel association is that I'm looking to
be able to say something like
>aroom.properties("NumOfSinks")
=>"2"
instead of
>aroom.roomproperties_dataset.filter(:key=>"NumOfSinks").map(:value).first
=>"2"
Here's what I've got, which seems to be working fairly well so far....
module SparseProperties
@@sparseproperty = nil # holds the associated property class.
@@fkey = nil # holds the column in the associated property class that is
the foreign key to the host class
# If there's a more elegant way to make .property work, I haven't been able
to figure it out.
class PropProxy
def initialize(parent)
@parentModel = parent
end
def [](key)
@parentModel.properties(key)
#.filter(:key => key).map(:value).first
end
def []=(key,value)
@parentModel.properties(key=>value)
end
end
def property
PropProxy.new(self)
end
def initializeSparse
unless @@sparseproperty then
@@sparseproperty = eval(self.class.to_s + "property")
@@sparseproperty.unrestrict_primary_key
end
if not @@fkey and not (reflector =
self.class.association_reflections.select{|key, val| val[:class_name] ==
@@sparseproperty.to_s}).empty? then
@@fkey = reflector.flatten[1][:key]
end
@sparseDataset =
@@sparseproperty.dataset.filter(@@fkey=>self[self.primary_key]) unless @dataset
@@sparseproperty && @@fkey && @sparseDataset
end
def properties_dataset
return nil unless initializeSparse
@sparseDataset
end
# invoked without an argument, 'properties' returns a hash of the sparse
properties for this record
# passing a single value, properties will assume it's a key, and return the
associated value (if any)
# At some point, I might extend it so that multiple args are recursively
processed, with an array
# being treated as multiple keys.
def properties(args=nil)
return nil unless initializeSparse
case args
when Hash then args.each do|k,v|
DB.transaction do
@sparseDataset.filter(:key => k).delete
@@sparseproperty.create(@@fkey=>self[self.primary_key], :key => k, :value=>v)
end
end
when Symbol then
@sparseDataset.filter(:key => args).map(:value).first
when String then
@sparseDataset.filter(:key => args).map(:value).first
when NilClass then
Hash[@sparseDataset.map([:key, :value])]
else
Hash[@sparseDataset.where(args).map([:key, :value])]
end
end
end
It bugs me to have to call initializeSparse for each use, but as far as I can
figure, modules can't set class variables, at least not without doing
mysterious meta-programming beyond my grasp. An earlier version of PropProxy
inherited from Hash, but then I have to override all five ways to set the
values, and I still have to reload it every time it's called in case the
database has been updated elsewhere, so that seemed overly-complicated. If one
wants to do clever hash-y things to the properties, just call .properties and
get the hash.
At some point I might replace the ...properties table with a PosgreSQL array or
the hstore datatype.
Comments? Warnings? Laughter?
--
You received this message because you are subscribed to the Google Groups
"sequel-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sequel-talk.
For more options, visit https://groups.google.com/d/optout.