Fork me on GitHub
  1. New scope index

    scope index

    The scope index allows you to keep a single index record tracking the state of many related nodes (sub-nodes in a Project or Section , nodes related through links, etc).

    This can be used with actions (acting like some kind of history) that update the state of a project or section or any other element that needs to keep a “poly-node” index.

    how it works

    You first create a new model for your index. The model should include the ScopeIndex module:

    class IdxContract < ActiveRecord::Base
      include Zena::Use::ScopeIndex::IndexMethods
    end
    

    Including the ScopeIndex module will also declare all indices from the record as safe attributes accessible with RubyLess.

    Then you should define the records you want to index. To define these records, simply write a migration:

    class CreateContractIndex < ActiveRecord::Migration
      def self.up
        create_table(:idx_contracts, :options => Zena::Db.table_options) do |t|
          # Mandatory fields
          t.integer  :site_id
          t.integer  :node_id
    
          # Fields to track the project itself
          t.integer  :contract_id
          t.integer  :contract_title
          t.integer  :contract_date
    
          # Fields to track the related contact
          t.integer  :contact_id
          t.integer  :contact_postal_code
          t.integer  :contact_vip
    
          # Fields to track the latest plan
          t.integer  :lastPlan_id
          t.string   :lastPlan_created_at
          t.float    :lastPlan_price
    
          # Fields to track the latest change in the project
          t.integer  :lastChange_id
          t.string   :lastChange_title
          t.datetime :lastChange_updated_at
    
          # Fields to track the latest invoice
          t.integer  :invoice_id
          t.string   :invoice_created_at
          t.float    :invoice_amount
          t.integer  :invoice_paid
        end
      end
    
      def self.down
        drop_table :idx_contracts
      end
    end
    

    The fields in the index contain a prefix to group attributes and filter what a given node should update. The key is matched against the keys in the idx_scope field.

    Finally, you should set the idx_class of the virtual class whose instances you want to index (Contract virtual class in our example) and set the index scope of all classes that should be used to update this index.

    The idx_scope should contain RubyLess code that represents a Hash . An idx scope looks like this:

    {'lastPlan,lastChange' => 'project or section'}
    

    Note that the “key” part of the Hash can be a list of ‘responsabilities’ separated by a comma.

    This definition should be read as:

    “as lastPlan and lastChange update the index of project or section”.

    The values in the Hash should be pseudo sql queries.

    Contract

    As we want to store some properties from the contract in the index, it should update its own index:
    idx_scope = {'contract' => 'self'}

    Plan

    The plan should update the contract’s index (project) when it is modified (both as lastPlan and lastChange):
    idx_scope = {'lastPlan,lastChange' => 'project'}

    Invoice

    The invoice’s idx_scope is:
    idx_scope = {'invoice,lastChange' => 'project'}

    Contact

    The contact should update the indices of its linked contracts (uses the “contract” relation): idx_scope = {'contact' => 'contracts'}

    index update

    Now that your index is in place, you can simply create a Contract and start using it. Every time the idx_scope (pseudo sql) of a Node points to our contract, it’s index will be updated.

    Only the “latest” node will update the index. This can be changed by overwriting the should_update_group? method in the index model (IdxContract ).

    query

    To query contracts with the index, use the index fields prefixed with the class name:

    contracts where invoice.paid = 0 and invoice.amount > 100 in site

    The example above would find all contracts for which an invoice greater the 100 is not paid. You could do the same without the scope index:

    projects from invoices where paid=0 and amount > 100 in site

    But then, you might want to do a query that involves different classes (Invoice and Contact for example) or your project might contain many changes (which you want to track) but you need to search by the latest only. This is where the scope index is needed:

    contracts where invoice.paid = 0 and plan.price > 1000 and contact.vip = 0 in site

    display index

    You might want to display the index content. In this case, you can simply use the safe method “scope_index” :

    <li>
      <span class='label'>plan amount</span>
      <span class='value' do='scope_index.plan_price'/>
    </li>

    A note on security

    You should consider all fields stored in the scope index as having the same access rights as the model they are connected to. In the example above, this means that “invoice.paid” and “plan.price” are readable even if you have special access rights for “invoice” or “plan” objects.

    Gaspard Bucher

    comments

    1. leave a comment