The whole thing started with what I thought would be a relatively routine Drupaly Apache Solry thing to do. We needed to enable faceted search on a nodereference field on four different nodes types that all pointed to a fifth node type. Think of the nodes that are pointed to as taxonomy terms only we couldn't use taxonomy terms because a single title field was not enough - we actually needed to flesh out a bit more information in CCK fields - where is a Drupal 7 when you need it, huh?.
I knew that the most excellent Drupal Apache Solr module could handle nodereference fields so didn't give it a second thought (advice to self: always give it a second thought).
We used a shared CCK field because we didn't want the Apache Solr module to produce four different filters (one for each field).
Fields created, index refreshed, single Solr filtered enabled, block in position, (thinking we are off to the pub early today...), refresh page... nothing. Says no nodes have that field. Ok, ok, no reason to worry. Must be a setting somewhere. Click on the block configuration of the filter and it says something about this filter applying to only one node type.

Hmm..... that's fishy. This field belongs to four different types. Let's hit the issue queues. Type in "shared cck fields" and first issue to pop up is titled CCK mappings don't respect shared fields. Oh, oh. A look at the discussion says a patch was committed to 6.2 but then a different path was suggested and looking at the actually code shows things anyway moved on since this issue was first mentioned.
Tried various things - poked at the code here and there - no luck. Some further research reveals a few more issues with nodereference - some thoroughly confusing discussions, all a natural and at times unavoidable side effect of the process and not somebody's fault.
So, anyway all this preamble was simply to get to the point where it is clear that we need to write our own mapping for this particular CCK field.
The first approach we followed was the one described by Robert Douglass here. However, this did not work because whatever problem was being caused before by handling a shared nodereference field we were having again.
After A LOT of search around I stumbled across this dead simple method. Part of it was described here, part here and the rest I discovered by looking at the apachesolr_og.module that comes with apachesolr.
The solution comes in three parts.
First up the index. We are calling hook_apachesolr_update_index (our little module is called gmp so you will see gmp in the place of hook everywhere). Update index gets called for every node being added to the index and allows you to define your own little field to add to the index.
In our case we check if the node being added is one of the four nodes that have the relevant nodereference field, load the related node and get the title. But the beauty of it is that we could get to any other field in the referenced node as well and add that to the Solr index.
function gmp_apachesolr_update_index(&$document, $node) {
if (isset($node->field_related_policy_process)){
if (($node->type == 'organization') ||($node->type == 'policydocument') || ($node->type == 'resource') || ($node->type == 'person')) {
foreach ($node->field_related_policy_process as $nodereference) {
$profile = node_load($nodereference['nid']);
$document->setMultiValue('sm_related_policy_process_gmp', $profile->title);
}
}
}
}
Done, update the index and we see our new Solr field in place.
Now we need to tell the Apache Solr module that this is a facet to use with the hook_apachesolr_facets. Very straightforward.
function gmp_apachesolr_facets() {
$facets['sm_related_policy_process_gmp'] = array(
'info' => t('Policy Process'),
'facet_field' => 'sm_related_policy_process_gmp',
);
return $facets;
}
You will now see the facet as an option in the admin page listing all the available facets.
Finally, and this is the part that got me confused because I didn't see it clearly stated anywhere and kind of assumed that the Apache Solr module would do it on its own, you need to implement hook_block so that you can actually see your facet as a block and place it in the search page.
function gmp_block($op = 'list', $delta = 0, $edit = array()) {
switch ($op) {
case 'list':
$enabled_facets = apachesolr_get_enabled_facets('gmp');
$facets = gmp_apachesolr_facets();
// Add the blocks
$blocks = array();
foreach ($enabled_facets as $delta => $facet_field) {
if (isset($facets[$delta])) {
$blocks[$delta] = $facets[$delta] + array('cache' => BLOCK_CACHE_PER_PAGE,);
}
}
return $blocks;
case 'view':
if (apachesolr_has_searched()) {
$response = apachesolr_static_response_cache();
if (empty($response)) {
return;
}
$query = apachesolr_current_query();
return apachesolr_facet_block($response, $query, 'sm_related_policy_process_gmp', $delta, $delta, t('Filter by Policy Process'), 'GMP policy process');
}
break;
case 'configure':
return apachesolr_facetcount_form('sm_related_policy_process_gmp', $delta);
case 'save':
apachesolr_facetcount_save($edit);
break;
}
}
Save, reload and that is it. You have a facet with just the things you wanted from a shared nodereference field all available for you.
If someone know of a better way or has spotted a problem please let me know.
Advice in case it may help someone:
1. Any documentation you see around treat with a pinch of salt especially if you are on a dev version of a module - things change and things go wrong so your only real documentation is the module itself. I know this is obvious but I need to remind myself of it!
2. Check out any contrib modules that come with the main module. They typically have been fixed and updated to the latest APIs so contain the solution you need. I only resolved my problem when I realised that I should look at apacheslor_og.module.
3. Never, ever, ever not think twice about something :-)

Comments
$delta, $delta
Hello there. Thank you kindly for this very helpful post.
I've been trying to put your code to work in my own context, and have almost succeeded. But not quite. I think
My nodereference facet block is showing up properly, but it isn't passing any nodereference arguments in the url string. I'm wondering if there a small errata in your gmp_block function. Is it intentional to pass the $delta parameter twice? or is there supposed to be something else in its place?
Cheers, and thank you again. Your post has been genuinely helpful to me.
Thanks
Thanks for the article. It's been pretty hard to find documentation covering things such as user/node/term refs as CCK fields with Solr. This article hits the spot and gives me confidence that it can be done :)
I'm interested in your decision to index the title of the profile, rather than the nid. It seems that this will be brittle as the profile title may change over time. I suspect that it may have been done so that the facets have pretty strings when filtering. I'm wondering how I'd work around this if I were to use the nid. It looks like theme_apachesolr_facet_link could be used to turn these nids into titles in the GUI but I'm not sure that that hook tells me enough to know that I was dealing with a nid. More research required. I'd be interested if anyone had comments on this aspect.
cheers, Murray
Small tweak
Finally got around to trying it and it is working a treat. Thanks.
I noticed that the signature for apachesolr_facet_block doesn't quite match up with code above.
apachesolr_facet_block($response, $query, $module, $delta, $facet_field, $filter_by, $facet_callback = FALSE)
If you need to handle multiple added facets the 'view' part of the block could be changed to the following to customise the filter block depending on the delta.
case 'view':
if (apachesolr_has_searched()) {
$response = apachesolr_static_response_cache();
if (empty($response)) {
return;
}
$query = apachesolr_current_query();
$facets = YOURMODULE_apachesolr_facets();
$facet = $facets[$delta])
return apachesolr_facet_block($response, $query, 'YOURMODULE', $delta, $facet['facet_field'], t($facet['info']));
}
break;
Add new comment