I am trying to write a module that will automatically set up a storage class and container when it is installed, and use hook_field_default_field_bases_alter() to enable it on certain fields in my site.

So far, the creation of the class and container works, and I can set the files I want to use the storage URI schemes I want.

The only thing I'm having trouble with is enabling my custom storage class on the field base definition. I'm setting the "class_id" parameter (using the code below) - but when I open up the field settings afterwards, the class is still set to "Everything" (the default class set up by Storage API itself).

In hook_field_default_field_bases_alter():

// Set the storage class of the field base.
$my_class_id = 2;
$data['field_farm_files']['settings']['storage_class'] = array(
  'class_id' => $my_class_id,
);

If I then go and manually configure the field to use the new class (via the UI), it works, and I notice that it is also storing a "current_class_id" property.

Do I need to set that property on the field base as well, in order for it to work?

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

m.stenta created an issue. See original summary.

Perignon’s picture

My suggestion would be to export the field with features to see what code is created with Features. Reason I say this is I have moved code around with Features that have fields backed by Storage API.

I have not wrote a custom module with a field using Storage API yet so I cannot be of much assistance.

m.stenta’s picture

Whelp - just answered my own question... no. Adding "current_class_id" does NOT work.

This is what I tried:

// Set the storage class of the field base.
$my_class_id = 2;
$data['field_farm_files']['settings']['storage_class'] = array(
  'class_id' => $my_class_id,
  'current_class_id' => $my_class_id,
);

So I'm not really sure how to do this correctly... :-/

m.stenta’s picture

I actually do have the fields stored in features already - and I'm trying to enable storage on them from a separate module (so that it can be turned on/off on some sites).

It seems like it needs more than just the field base definition to set the storage class... or something... I'm digging into it now to try to figure out what's up.

Perignon’s picture

Yes there are changes in the Storage API tables that has to be made.

m.stenta’s picture

Ah ok! I think I see what you're referring to... in the {storage_selector} table?

Do you know if there's a helper function to populate that? Or should I just do some straight queries to set those up?

Perignon’s picture

Honestly I cannot say. I am just a co-maintainer here and I jumped into a rat's nest of a module :-D

m.stenta’s picture

Status: Active » Closed (fixed)

Got it!

You were right about the {storage_selector} table - it needed to have the class set there too.

Here is the final hook implementation that works for me (generalized for the purpose of sharing):

/**
 * Implements hook_field_default_field_bases_alter().
 */
function mymodule_field_default_field_bases_alter(&$data) {

  // Find the "My Storage Class" class that was created by Storage API.
  $result = db_query("SELECT class_id FROM {storage_class} WHERE name = 'My Storage Class'");
  $class_id = $result->fetchField();

  // Alter fields to use the storage class.
  $fields = array(
    'field_mymodule_files',
    'field_mymodule_images',
  );
  foreach ($fields as $field) {
    if (isset($data[$field])) {

      // Set the URI scheme.
      $uri_scheme = 'storage-' . str_replace('_', '-', $field);
      $data[$field]['settings']['uri_scheme'] = $uri_scheme;

      // Set the storage class.
      $data[$field]['settings']['storage_class'] = array(
        'class_id' => $class_id,
      );

      // Ensure that the {storage_selector} record is set to the class.
      db_update('storage_selector')
        ->fields(array(
          'class_id' => $class_id,
        ))
        ->condition('selector_id', 'storage_core_bridge:field:' . $field)
        ->execute();
    }
  }
}

Not sure if that's the "correct" way to do it (there may be helper functions in the Storage module that do the same thing in a more consistent way - I didn't dig deep enough to rule that out)... but hopefully this helps anyone who's looking to do the same thing!

jonhattan’s picture

m.stenta’s picture

Awesome! Thanks @jonhattan! I had a feeling there would be...

Question...

In the selector->create() method, it looks like it always performs a database INSERT into the {storage_selector} table.

In my code, I was performing an UPDATE, because there is already a record in the {storage_selector} table using the "Everything" class by default. But I want to change the class... not add another one... I think.

Why does the {storage_selector} table allow multiple records per storage_id? The primary key of the table is array('storage_id, 'class_id') - which indicates that storing multiple combinations is OK. I don't understand the purpose of this - do you? I think I haven't fully wrapped my head around what a "selector" is.

jonhattan’s picture

It does an insert, because it *creates* a selector. Problem is that your approaching with a _alter, that runs always. You want to check first if a storage selector already exists.

A selector is the interface to manage storage api files. A selector is related to a storage class.... and this relation can safely be changed (in the UI), and storage api migrate files from one class to another. Files continue attached to the same selector, but moved to any other class/containers.

storage_core_bridge creates a selector and a stream wrapper for each field. storage_api_stream_wrapper also works with selectors and stream wrappers, but in a different way.

Perignon’s picture

Yeah pretty crappy I don't know this module that well.

m.stenta’s picture

Category: Support request » Feature request
Status: Closed (fixed) » Needs review
FileSize
808 bytes

Thanks @jonhattan.

The function storage_core_bridge_field_selector_create($field_name) lacks any way of setting the class_id that you want to assign. If you trace the logic into the selector->create() method it just grabs the first one it finds (which in all cases will be the "Everything" class - since that's the first one created automatically by the Storage module).

Line 27 of selector.inc:

$class_id = db_select('storage_class')
        ->fields('storage_class', array('class_id'))
        ->orderBy('name')
        ->orderBy('class_id')
        ->range(0, 1)
        ->execute()
        ->fetchField();

So I guess the best solution would be to add an optional $class_id parameter to the storage_core_bridge_field_selector_create() function.

Attached is a patch for review!

m.stenta’s picture

Status: Needs review » Needs work

I just tested that patch - and that alone does not solve my problem.

That allows me to add additional rows to the {storage_selector} table for my custom "My Files" class, alongside the existing ones for the "Everything" class. But if I go to the field UI and look at the configuration, they are using "Everything" instead of "My Files".

If I use the UPDATE query I described in #8, it works perfectly - because it overwrites the "Everything" class in the {storage_selector} so that ONLY "My Files" is used in that table.

I'm not sure if this is possible with the current API functions... is it?

m.stenta’s picture

PS: Here is the code I tested in my hook_field_default_field_bases_alter():

$exists = db_query('SELECT COUNT(*) FROM {storage_selector} WHERE selector_id = :selector AND class_id = :class', array(':selector' => 'storage_core_bridge:field:' . $field, ':class' => $class_id))->fetchField();
if (empty($exists)) {
  storage_core_bridge_field_selector_create($field, $class_id);
}

That works to add additional rows to the {storage_selector} table - but it does not fix the fact the all my fields are defaulting to using the "Everything" class.

jonhattan’s picture

I agree StorageSelector::create() could check for existance before db_insert. Perhaps with a db_merge - https://www.drupal.org/node/310085