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.

Reply via email to