Fork me on GitHub

indexing

An article by Gaspard Bucher

Of course storing raw data is cool, but what happens if you need to sort ? or build queries related to the values in the property hash ?

You have three main choices to index content:

  1. create key/value tables
  2. use fields in the object’s table reserved for indexing (in Zena, these fields start with “idx_” (idx_datetime1, idx_integer1, etc).
  3. create a custom class

key/value tables

class AddStringIndexToContact < ActiveRecord::Migration
  def self.up

    # Read the name as "index contacts with strings"
    create_table 'idx_contacts_strings' do |t|
      t.integer 'contact_id'
      t.string  'key'
      t.string  'value'
    end
  end

  def self.down
    drop_table :idx_string_contacts
  end
end

When you have migrated your database, you can start setting indices on your properties, either by adding :indexed => true or by using a Proc :

class Contact < ActiveRecord::Base
  include Property
  property do |p|
    p.string 'last_name', :indexed => true
    p.string 'first_name', :index => Proc.new {|rec| { 'fullname' => rec.fullname }}

    p.index(:string) do |record|
      {
        'fulltext' => "name:#{record.name} first_name:#{record.first_name}",
        "name_#{record.lang}" => record.name
      }
    end
  end
end

You can also use a Proc on a single property or build dynamic keys. In fact you can do whatever you want inside the index block as long as you return a Hash with string keys pointing to values compatible with the type for ‘value’ in your corresponding table (:string ==> i_string_…).

field indexing

Once you have created some index fields in the table (“contacts” in this case), you simply have to prefix the field name with ”.” when defining the index:

class Contact < ActiveRecord::Base
  include Property
  property do |p|
    p.string 'first_name'
    p.string 'last_name', :index => '.idx_strings1'
  end
end

This does not look very interesting but it really becomes useful when you use single table inheritance and versioning (properties are not stored in the same table as the index fields).

indexing with a custom class

Sometimes key/value storage is not optimal for indices and you want to group values together on single record or use a legacy table (maybe the one used before Property). You can achieve this easily:

class Contact < ActiveRecord::Base
  include Property
  property do |p|
    p.string 'last_name'
    p.string 'first_name'

    p.index(ContactIndexer) do |record|
      {
        'last_name' => record.last_name,
        'first_name' => record.first_name,
      }
    end
  end
end

# And here is the class to manage the index
class ContactIndexer < ActiveRecord::Base

  def self.set_property_index(contact, indices)
    if index = first(:conditions => ['contact_id = ?', contact.id])
      index.update_attributes(indices)
    else
      create(indices.merge(:contact_id => contact.id))
    end
  end

  def self.delete_property_index(contact)
    delete_all(['contact_id = ?', contact.id])
  end
end

When you need to search and sort using the keys defined here, the easiest way is to create a query builder processor that knows about the index classes (see Zena’s QueryNode processor for example).