Hi there,

I'm trying to index the Money module (a CCK field for amount+currency). Next I want to filter the data by a range, e.g. 10 < price < 1000. Ultimately I want to provide 2 blocks, one price range block for amount, another filter block for currency.

As I'm trying to build this, I'll document my steps and perhaps people can help out.

Step 1. Money module
---------------------
http://drupal.org/project/money

The module allows to store two values, and multiple values are allowed, like this:

$node->field_money[] = array('amount' => '10.00', 'currency' => 'EUR');
$node->field_money[] = array('amount' => '12.0000', 'currency' => 'USD');

The accuracy of the amount is currency-specific(!). E.g. it could have 2,3,4 or more decimals after the point. The money module handles this.

Learning from #283859: How to define addtional fields in schema.xml?, this is my best guess to setup the dynamic index, in my custom.module:

function custom_apachesolr_update_index(&$document, $node, $namespace) {
  $key = apachesolr_index_key(array('index_type' => 'double', 'multiple' => TRUE, 'name' => 'field_money_amount'));
  $document->{$key} = $node->field_money[0]['amount'];

  $key = apachesolr_index_key(array('index_type' => 'string', 'multiple' => TRUE, 'name' => 'field_money_currency'));
  $document->{$key} = $node->field_money[0]['currency'];
}

I'm not sure if this is the correct start. And I'm not sure how to handle multiple values either. As you can see, I am using two field names, _amount and _currency.

(to be continued)

CommentFileSizeAuthor
#4 apachesolr_money_v2.zip2.4 KBAnonymous (not verified)
#2 apachesolr_money.zip1.76 KBAnonymous (not verified)

Comments

Anonymous’s picture

Step 2. ND Search, from the Node Display / Display Suite
-------------------------------------------------------
We need to index the money CCK field. I borrowed code from the ND Search modules from the Display Suite module.

I adapted their approach to get the right values from the Money CCK field, in a custom.module:

/**
 * Implementation of hook_apachesolr_cck_fields_alter().
 *
 * Add the some CCK fields to apache solr index.
 */
function custom_apachesolr_cck_fields_alter(&$mappings) {
  $fields = content_fields();
  
  foreach ($fields as $field) {
    $type = _custom_get_solr_type($field['type']);
    if ($type != FALSE) {     
      // Morningtime: money field is different, it's actually two fields
      if ($type == 'money') {
        $mappings['per-field'][$field['field_name']] = array( // the basic field for amount
          'display_callback' => '',
          'indexing_callback' => 'custom_indexing_money_amount_callback',
          'index_type' => 'float',
        );
        $mappings['per-field'][$field['field_name'].'_currency'] = array(
          'display_callback' => '',
          'indexing_callback' => 'custom_indexing_money_currency_callback',
          'index_type' => 'string',
        );
      } else {
        $mappings['per-field'][$field['field_name']] = array(
          'display_callback' => '',
          // The callback function gets called at indexing time to get the values.
          'indexing_callback' => 'nd_search_indexing_callback',
          // Common types are 'text', 'string', 'integer',
          // 'double', 'float', 'date', 'boolean'
          'index_type' => _custom_get_solr_type($field['type']),
        );
      }
  
    }
  }  
}

The following part detects the money field type, as called above:

/**
 * Get the corresponding apache solr type from a cck type.
 *
 * @param string $type CCK field type.
 * @return array with type and prefix in apache solr.
 */
function _custome_get_solr_type($type) {
  $types = array(
    'text' => 'string',
    'number_integer' => 'integer',
    'number_float' => 'float',
    'filefield' => 'string',
    'imagefield' => 'string',
  );

  // Let's skip date, see http://drupal.org/node/558160
  if ($type == 'date' || $type == 'datetime' || $type == 'datestamp') {
    return FALSE;
  }
  
  // Morningtime:
  if ($type == 'money') {
    return $type;
  }
 
  // Default type is text
  if (!isset($types[$type])) {
    return 'string';
  }

  return $types[$type];
}

Finally, you need your callbacks to map the actual amount and currency.

/**
 * Put all the needed CCK fields in the index.
 *
 * @param stdClass $node The current node being indexed.
 * @param string $fieldname The current field being indexed.
 * @return an array of arrays. Each inner array is a value, and must be keyed 'safe' => $value.
 */
function custom_indexing_money_amount_callback($node, $fieldname) {
  $fields = array();

  foreach ($node->$fieldname as $field) {
    if (isset($field['amount'])) {
      $fields[] = array('value' => $field['amount']);
    }
  }
    
  return $fields;
}

function custom_indexing_money_currency_callback($node, $fieldname) {
  $fields = array();

  foreach ($node->$fieldname as $field) {
    if (isset($field['currency'])) {
      $fields[] = array('value' => $field['currency']);
    }
  }
    
  return $fields;
}

-------

With all this code in place I get a first result! I can go to /admin/reports/apachesolr and see for instance that ps_field_money_amount exists and is indexed. In my case 150 unique values. Perfect.

But the Facet Block does not work, it shows one facet with "Missing this field (150)". So, next step (tomorrow or later) will be to fix the block, to get it to read data from the new field_money_amount index.

Anonymous’s picture

StatusFileSize
new1.76 KB

Here's my first result as a contrib module. So far it correctly indexes the Money CCK fields of choice, you can select the filters on admin/settings/apachesolr/enabled-filters in the fieldset "Apache Solr Money". For each CCK field there are two options: Field (Amount) and Field (Currency).

To do:
- create filter blocks (I'm not sure why they aren't created automatically)
- create price range filter (min < price < max)

P.S. At the top of apachesolr_money.module you'll see:

function apachesolr_money_apachesolr_prepare_query(&$query) {
  $fields = apachesolr_cck_fields(); 
  $query->set_available_sort('ps_field_product_price', array(
    'title' => t('Price'),
    'default' => 'asc',
  ));
}

This is a custom sort implementation, I hardcoded my CCK field Solr index name "ps_field_product_price". You should change this to get sorting to work.

I'd like it if someone else had a look at it. And I really need help creating the "Price Range" facet block.

Anonymous’s picture

Status: Active » Needs work

Just for reference, the final idea is to create a price range filter like on http://www.thefind.com/search?query=drupal (top right "Prices").

Anonymous’s picture

StatusFileSize
new2.4 KB

Update: the attached contrib module now shows the Filter Blocks, Amount and Currency, for each enabled money CCK facet. Also sorting by price is no longer hard-coded.

yeeloon’s picture

hi there,

do you have a demo site set-up for this module?

Anonymous’s picture

Yeah now I do,

http://www.icescan.com/s/Query

See the money slider in the top right. It pulls the min/max for the current search from solr, so the slider is always "most relevant" to your query. It's built with regular jQUery 1.2x / 1.3x "slider" plugin, from the jquery.ui.

Operations-1’s picture

Hello morningtime. I'm developing almost the exact same system as you, but for the brazilian market so, no competition :). Very nice what you did with the price range! I can see that you really know what you are doing! Can you specify better how did you achieve this price range facet with solr? Can you give a more step-by-step instructions? Or at least point me to the right direction?

Thanks

Anonymous’s picture

@Operations,

we will compete then, because I will also target Brazil.

But when I'm done, I will release my system as a Drupal installation profile. This may take about 12 months though, because I have to focus on building my business first.

Anonymous’s picture

Status: Needs work » Closed (fixed)

Closing this, I fixed my issues with a custom solution.

Anonymous’s picture

I now published my solution as a module, Apache Solr Money Slider,
http://drupal.org/project/apachesolr_money