Hi,

After trying out this module for a project, I found that the deployment of content with field collections fails. I see the following error in the service response:

PDOException: SQLSTATE[HY000]: General error: 1366 Incorrect integer value: 'efb63753-06db-462f-98cd-165c462b8b14' for column 'field_grid_content_value'

I think this bug is related to this issue http://drupal.org/node/1541218.

The generated UUID of the field collection is not converted to a entity ID in the destination installation. I'm not sure if this is a bug for the UUID module or this module. What is a problem, is that the field collection data is not included in the JSON object sent to the rest service, or added to the deployment plan. Either solution could work, depending on the way the services module handles this.

There is an issue (with patch) for services support in the field collection module http://drupal.org/node/1180574. This issue is also related in the way deployment should deliver data to the webservice in a way supported by the module.

This will probably not be easy, but at least there is an issue to track the progress.

Comments

xadag’s picture

I had the same problem, i have the same response from my service

http://drupal.org/node/1820606

It use the uuid for entity id and try to save it in the database

ohthehugemanatee’s picture

I have the same problem. Actually, field_collection also has trouble with Services in general, but there's a nice patch in #11 at #1180574: Integration with Services which provides a perfectly reasonable implementation of services for field_collection. We could use that schema for Deploy, and between the two patches have the problem licked.

Here's the step-by-step:

1) Figure out if we're adding a field collection
2) load the related field collection entities into the queue to be deployed. We can probably rip behavior from related entity handling for this
3) structure the JSON output like the example from #1180574: Integration with Services

darrenmothersele’s picture

Would an alternative method be to implement the hooks in entity_dependency and then provide a field_collection_item service? I'll give this a shot and then report back.

darrenmothersele’s picture

The entity dependency is already implemented by field_collection, so when deploying an entity with a field_collection it does already attempt to deploy the field_collection entities first. But deploy fails because there is no service for field_collection_items on the destination site.

I created a service resource to receive the field collection items, but it fails because the field collection items can't be created without a host entity, and they are being deployed first because of the entity_dependency.

I'm not sure what to do next? It seems like the entity containing the field collection would have to be deployed first, but this is not what happens with the implementation of hook_entity_dependencies().

darrenmothersele’s picture

I've managed to get field collections to deploy successfully, but only by overriding the FieldCollectionItemEntity class to prevent the default behaviour of throwing an exception if you try to save a field collection item without a host entity. This means that you can use the provided entity dependency to deploy field collection items first, and save them without the exception being thrown. Then, when the entity with the field collection field is deployed the items are already available to attach.

I'll post some example code once I get this tidied up.

teranex’s picture

@darrenmothersele Would you care to share what you already have? I'm having the exact same problem so it would be nice if I could test your solution. thx!

seanb’s picture

Great to see there is progress so quickly! I'm also very curious what you made @darrenmothersele. If you could share this would be very helpfull!

darrenmothersele’s picture

Here's what I did to get Field collection items deploying. First override the default entity class with our own...

function MODULE_entity_info_alter(&$entity_info) {
  $entity_info['field_collection_item']['entity class'] = 'MODULEFieldColletionItemEntity';
}

Then, in this custom field collection item controller class, override the default behaviour that prevents a Field Collection item from being saved without it's host entity. This is required because when deploying, you deploy the dependencies first (i.e. the field collection items before the host entity)...

class MODULEFieldColletionItemEntity extends FieldCollectionItemEntity {

  public function __construct(array $values = array(), $entityType = NULL) {
    parent::__construct($values, $entityType);
  }

  public function save($skip_host_save = FALSE) {
    if (isset($this->hostEntity)) {
      // If we have a host entity save normally
      parent::save($skip_host_save);
    } else {
      // If there is no host entity, we may be receiving it from deploy
      // so save the item directly (and force revision id to match item id)
      $this->revision_id = $this->item_id;
      entity_get_controller($this->entityType)->save($this);
    }
  }
}

Finally, on the receiving end I use hook_services_request_preprocess_alter() to convert the UUID of the field collection items in the field to a local ID of the field collection items. The entity dependency will have ensured that the field collection items have been deployed first...

function MODULE_services_request_preprocess_alter($controller, &$args) {
  foreach ($args as $i => $arg) {
    // TODO: Make this generic - currently hard coded to my node type
    if (is_array($arg) && isset($arg['type']) && $arg['type'] == 'my_node_type') {
      foreach ($arg['my_field_collection_field'][LANGUAGE_NONE] as $delta => $value) {
        $target = reset(entity_get_id_by_uuid('field_collection_item', array($value['value'])));
        $args[$i]['my_field_collection_field'][LANGUAGE_NONE][$delta] = array(
          'value' => $target,
          'revision_id' => $target,
        );
      }
    }
  }
}

It's not pretty (hard coding my node type, etc), but it works.

zach.bimson’s picture

Thanks for this! looks really good.
Do you have an example json node that works with this?

Would help alot!

picardmyhero’s picture

picardmyhero’s picture

Component: Miscellaneous » Code

After discussing this issue with Darren via email, here is a generic version of the last function of his solution...

function MODULE_services_request_preprocess_alter($controller, &$args) {
  foreach ($args as $i => $arg) {
    if (is_array($arg) && isset($arg['type'])) {
      foreach (field_info_fields($arg['type']) as $fieldValue) {
        if ($fieldValue['type'] == "field_collection") {
                foreach ($arg[$fieldValue['field_name']][LANGUAGE_NONE] as $delta => $value) {
                        $target = reset(entity_get_id_by_uuid('field_collection_item', array($value['value'])));
                        $args[$i][$fieldValue['field_name']][LANGUAGE_NONE][$delta] = array(
                                'value' => $target,
                                'revision_id' => $target,
                        );
                }
        }
      }
    }
  }
picardmyhero’s picture

Status: Active » Closed (duplicate)
picardmyhero’s picture

Component: Code » Miscellaneous
Status: Closed (duplicate) » Active
zach.bimson’s picture

Could you please provide a generic JSON object that you'd pass to get this to work ?
Lets assume you have two fields in the collection called field_name and field_age..
Help would be much appreciated

jrobison’s picture

Guys, I tried the solution mentioned above and it does fix the error, however it doesn't seem to deploy the field collection items correctly.

What I am seeing is that the revision_id in the field_collection_item_revision table is actually an auto_increment field and thus the revision_id set everywhere else doesn't match the actual revision id that gets created. Because of this it would seem that the field collection items doesn't show up when editing / viewing the parent node in the destination environment.

I tried changing this:

<?php
function MODULE_services_request_preprocess_alter($controller, &$args) {
  foreach ($args as $i => $arg) {
    if (is_array($arg) && isset($arg['type'])) {
      foreach (field_info_fields($arg['type']) as $fieldValue) {
        if ($fieldValue['type'] == "field_collection") {
                foreach ($arg[$fieldValue['field_name']][LANGUAGE_NONE] as $delta => $value) {
                        $target = reset(entity_get_id_by_uuid('field_collection_item', array($value['value'])));
                        $args[$i][$fieldValue['field_name']][LANGUAGE_NONE][$delta] = array(
                                'value' => $target,
                                'revision_id' => $target,
                        );
                }
        }
      }
    }
  }
?>

to this:

<?php
function MODULE_services_request_preprocess_alter($controller, &$args) {
  foreach ($args as $i => $arg) {
    if (is_array($arg) && isset($arg['type'])) {
      foreach (field_info_fields($arg['type']) as $fieldValue) {
        if ($fieldValue['type'] == "field_collection") {
                foreach ($arg[$fieldValue['field_name']][LANGUAGE_NONE] as $delta => $value) {
                        $target = reset(entity_get_id_by_uuid('field_collection_item', array($value['value'])));
                        $args[$i][$fieldValue['field_name']][LANGUAGE_NONE][$delta] = array(
                                'value' => $target,
                                'revision_id' => db_next_id(db_query('SELECT MAX(revision_id) FROM {field_collection_item_revision}')->fetchField()),
                        );
                }
        }
      }
    }
  }
?>

However, this just seems to give me the revision id from the source table and not the destination table.

Any suggestions? Perhaps there's a way to call the next id on the the destination table to get this id?

jrobison’s picture

Looks like I misunderstood how the db_next_id works.

This fixed my issue:

function gfcore_entity_info_alter(&$entity_info) {
  $entity_info['field_collection_item']['entity class'] = 'GFCoreFieldCollectionItemEntity';
}

function gfcore_services_request_preprocess_alter($controller, &$args) {
  foreach ($args as $i => $arg) {
    if (is_array($arg) && isset($arg['type'])) {
      foreach (field_info_fields($arg['type']) as $fieldValue) {
        if ($fieldValue['type'] == "field_collection") {
          foreach ($arg[$fieldValue['field_name']][LANGUAGE_NONE] as $delta => $value) {
            $target = reset(entity_get_id_by_uuid('field_collection_item', array($value['value'])));
            $args[$i][$fieldValue['field_name']][LANGUAGE_NONE][$delta] = array(
              'value' => $target,
              'revision_id' => db_query('SELECT MAX(revision_id) FROM {field_collection_item_revision}')->fetchField()
            );
          }
        }
      }
    }
  }
}

class GFCoreFieldCollectionItemEntity extends FieldCollectionItemEntity {

  public function __construct(array $values = array(), $entityType = NULL) {
    parent::__construct($values, $entityType);
  }

  public function save($skip_host_save = FALSE) {
    if (isset($this->hostEntity)) {
      // If we have a host entity save normally
      parent::save($skip_host_save);
    } else {
      // If there is no host entity, we may be receiving it from deploy
      // so save the item directly (and force revision id to match item id)
      $this->revision_id = db_query('SELECT MAX(revision_id) FROM {field_collection_item_revision}')->fetchField();
      entity_get_controller($this->entityType)->save($this);
    }
  }
}
mpgeek’s picture

If anyone has tried this with fetch-only plans, i came up with a quick workaround that makes deployments via site install work. It is here #1997712: Alternative solution for deploying field collections via features (fetch only plans). I cannot speak to service/endpoint-based deployments, but the Features-based workflow only on install is functional. I'm trolling for input on a more general solution, and whether or not something should be patched.

timaholt’s picture

So the code in #16 does seem to work in my testing, however it seems to be looping every possible field on each entity in the deploy regardless of whether or not that field is present in the entity. This leads to a lot of php warnings and notices in watchdog. I've tweaked the hook_services_request_preprocess_alter to replace the call to field_info_fields() with a call to field_info_instances. This ensures that only the fields that are attached to the entity are looped through.

function MYMODULE_services_request_preprocess_alter($controller, &$args) {
  foreach ($args as $i => $arg) {
    if (is_array($arg) && isset($arg['type'])) {
      foreach (field_info_instances($args[0], $arg['type']) as $fieldValue) {
        if (isset($fieldValue['type']) && $fieldValue['type'] == "field_collection") {
          foreach ($arg[$fieldValue['field_name']][LANGUAGE_NONE] as $delta => $value) {
            $target = reset(entity_get_id_by_uuid('field_collection_item', array($value['value'])));
            $args[$i][$fieldValue['field_name']][LANGUAGE_NONE][$delta] = array(
              'value' => $target,
              'revision_id' => db_query('SELECT MAX(revision_id) FROM {field_collection_item_revision}')->fetchField()
            );
          }
        }
      }
    }
  }
}
dixon_’s picture

Project: Deploy - Content Staging » Universally Unique IDentifier
Version: 7.x-2.x-dev » 7.x-1.x-dev
Component: Miscellaneous » Code

So this should be implemented on the destination side, i.e. for uuid_services module.

Someone (I think it was @skwashd) added some stub code for this way back (see uuid_services/resources/field_collection.resource.inc).

Any takers on creating an actual patch for this?

mpgeek’s picture

What about fetch-only plans? It's my understanding that fetch only means no services at the endpoint, and Features does the "import" of content via features-revert. Is there a way this can be implemented more generally so that both methods of deployment will work with field collection? I would be a taker on creating a patch, but I would be pulling for maximum coverage on all use cases.

jamesharv’s picture

Status: Needs review » Active
StatusFileSize
new3.92 KB

Here's a patch that I have managed to get working. I took a different approach to handling the revision_id. In field_collection_field_uuid_presave() I just load all the field_collection_item entities that are stored in a field and grab their revision_ids.

This works because the field_collection_item entities have already been saved, and will have been assigned a new revision_id at that time.

I also flag field_collection_item entities as having been universalized by setting $entity->__uuid_universalized = TRUE. This flag is then checked in the new entity controller to ensure that I only bypass the normal controller logic for entities which have been universalized.

jamesharv’s picture

Status: Active » Needs review
timaholt’s picture

Status: Active » Needs work

Can you re-roll this patch? It doesn't apply to alpha4, alph5 or to latest dev.

jamesharv’s picture

Hi @timaholt,

The patch was written against 7.x-1.x, and it does apply cleanly for me. It also applies cleanly for me against alpha5, see below:

Applied against 7.x-1.x branch:

james:~ $ git clone --branch 7.x-1.x http://git.drupal.org/project/uuid.git
Cloning into 'uuid'...
remote: Counting objects: 909, done.
remote: Compressing objects: 100% (732/732), done.
remote: Total 909 (delta 568), reused 276 (delta 172)
Receiving objects: 100% (909/909), 173.21 KiB | 88 KiB/s, done.
Resolving deltas: 100% (568/568), done.
james:~ $ cd uuid
james:~/uuid $ wget https://drupal.org/files/field-collection-support-1817956-21.patch
--2013-07-18 09:19:03-- https://drupal.org/files/field-collection-support-1817956-21.patch
Resolving drupal.org... 140.211.10.16, 140.211.10.62
Connecting to drupal.org|140.211.10.16|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 4012 (3.9K) [text/plain]
Saving to: `field-collection-support-1817956-21.patch'

100%[====================================================================================================================================================================================================================================================================================>] 4,012 --.-K/s in 0s

2013-07-18 09:19:06 (1.87 GB/s) - `field-collection-support-1817956-21.patch' saved [4012/4012]

james:~/uuid $ git apply -v field-collection-support-1817956-21.patch
Checking patch controllers/UUIDFieldCollectionItemEntity.inc...
Checking patch uuid.core.inc...
Checking patch uuid.entity.inc...
Checking patch uuid.info...
Applied patch controllers/UUIDFieldCollectionItemEntity.inc cleanly.
Applied patch uuid.core.inc cleanly.
Applied patch uuid.entity.inc cleanly.
Applied patch uuid.info cleanly.
james:~/uuid $ git st
# On branch 7.x-1.x
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: uuid.core.inc
# modified: uuid.entity.inc
# modified: uuid.info
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# controllers/
# field-collection-support-1817956-21.patch
no changes added to commit (use "git add" and/or "git commit -a")
james:~/uuid $

Applied against 7.x-1.0-alpha5 tag:

james:~ $ git clone --branch 7.x-1.0-alpha5 http://git.drupal.org/project/uuid.git
Cloning into 'uuid'...
remote: Counting objects: 909, done.
remote: Compressing objects: 100% (732/732), done.
remote: Total 909 (delta 568), reused 276 (delta 172)
Receiving objects: 100% (909/909), 171.48 KiB | 82 KiB/s, done.
Resolving deltas: 100% (568/568), done.
Note: checking out 'a383295fd6cdb87ca90cc6c1907a5ea868da16d7'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b new_branch_name

james:~ $ cd uuid
james:~/uuid $ wget https://drupal.org/files/field-collection-support-1817956-21.patch
--2013-07-18 09:21:54-- https://drupal.org/files/field-collection-support-1817956-21.patch
Resolving drupal.org... 140.211.10.16, 140.211.10.62
Connecting to drupal.org|140.211.10.16|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 4012 (3.9K) [text/plain]
Saving to: `field-collection-support-1817956-21.patch'

100%[====================================================================================================================================================================================================================================================================================>] 4,012 19.1K/s in 0.2s

2013-07-18 09:21:56 (19.1 KB/s) - `field-collection-support-1817956-21.patch' saved [4012/4012]

james:~/uuid $ git apply -v field-collection-support-1817956-21.patch
Checking patch controllers/UUIDFieldCollectionItemEntity.inc...
Checking patch uuid.core.inc...
Checking patch uuid.entity.inc...
Checking patch uuid.info...
Applied patch controllers/UUIDFieldCollectionItemEntity.inc cleanly.
Applied patch uuid.core.inc cleanly.
Applied patch uuid.entity.inc cleanly.
Applied patch uuid.info cleanly.
james:~/uuid $ git st
# Not currently on any branch.
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: uuid.core.inc
# modified: uuid.entity.inc
# modified: uuid.info
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# controllers/
# field-collection-support-1817956-21.patch
no changes added to commit (use "git add" and/or "git commit -a")

timaholt’s picture

Status: Needs work » Needs review

You're right, what is odd here is that the patch applies cleanly with a git clone, but fails to apply in a drush make file (I do all my testing locally on a build using drush make). I suspect this is a permissions thing since the patch creates a directory. I will test this as soon as I can.

timaholt’s picture

So i could never get this patch to work, but i did use this code in creating a field_collection_uuid module to put this in. It solves the original problem, but now I'm having issues with revisions on field collection items. Immediately after using this code, then creating a new revision on a node with a field_collection in it, I get a an SQL error asking for base.vuuid on the node table that the field collection was in. So then I created the field_collection_uuid module to put vuuid's on the field_collection_item_revision table, and that is works as expected. But trying to deploy a revision of a field_collection item shifted the base.vuuid error over to the field_collection_item_revision table:

PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'base.vuuid' in 'where clause': SELECT revision.revision_id AS revision_id, revision.vuuid AS vuuid, base.item_id AS item_id, base.field_name AS field_name, base.archived AS archived, base.uuid AS uuid, base.revision_id = revision.revision_id AS default_revision FROM {field_collection_item} base INNER JOIN {field_collection_item_revision} revision ON revision.revision_id = base.revision_id WHERE (base.item_id IN (:db_condition_placeholder_0)) AND (base.vuuid = :db_condition_placeholder_1) ;

Has anyone come across this before? I've been going down a rabbit hole trying to figure this out...but so far without much luck.

timaholt’s picture

So my above issue turned out to be with the deploy-revisions branch of deploy and trying to load a specific revision of an entity by it's vuuid in the entity_uuid_load function. Linked post here: https://drupal.org/node/2066401

Back to field collections, @jamesharv your code seems to work but I'm thinking perhaps we should move this all to a field_collection_uuid module and remove the load/presave functions from the UUID module. There is a similar module for Bean called Bean UUID that does the same thing. Ultimately it would be cleaner to have that code local to each contrib module, instead of trying to maintain it all inside the main UUID module.

Thoughts?

timaholt’s picture

@jamesharv: I've put a new issue into the field_collection issue queue with a patch that provides all this as a submodule to field_collection called field_collection_uuid. Can you test this? I feel this is the better way to approach this, and combined with the issue on removing contrib support from UUID, it works perfectly in all my testing.

Field Collection UUID issue and patch: https://drupal.org/node/2075325

Removing contrib functions from UUID: https://drupal.org/node/2074599

robert castelo’s picture

Issue summary: View changes

For anyone still looking for a solution - Field Collection Deploy module:

https://drupal.org/project/field_collection_deploy

miroslavbanov’s picture

The problem is not only in deployment, and can easily be reproduced with a few simple lines of code.
I think we need a clear test to explain and an easy to reproduce steps.
To reproduce:

  1. Have a node with at least one field collection item on it.
  2. Load the node with entity_uuid_load('node', array($uuid));
  3. Save the node.

I've created a very simple test - because of dependency has to be a separate (sub) module.

miroslavbanov’s picture

StatusFileSize
new2.86 KB

Oops, here's the real patch

The last submitted patch, 30: field-collection-support-1817956-30-testonly.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 31: field-collection-support-1817956-31-testonly.patch, failed testing.

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 31: field-collection-support-1817956-31-testonly.patch, failed testing.

miroslavbanov’s picture

Well, I'm not sure how to create a test that requires another module. My steps to reproduce still are relevant though.

The last submitted patch, 21: field-collection-support-1817956-21.patch, failed testing.

mrmikedewolf’s picture

This is a reroll of 21 that will work with drush patchfile.

boobaa’s picture

Contrib module support was removed in #2074599: Remove all Contrib module functions from uuid.core.inc, plus reference where UUID support is for contrib modules, so this patch no longer applies. Rerolling it isn't straightforward, either, because of the same reason.

skwashd’s picture

Project: Universally Unique IDentifier » UUID Extras

Moving to UUID Extras.

boobaa’s picture

My goal was to be able to use Deploy to push nodes with Field Collection items on them to a target site which uses Services. After quite some struggling, I could get it work as expected. I collected the ideas from quite some places, including, but not limited to:

I'm using

The uuid.entity.inc file in UUID module needs to be patched because so many things call uuid_get_core_entity_info() directly in different places, and there is no other way to add the info which the patch adds (and the field_collection patch for uuid_extras relies on). If UUID folks really want to get rid of the contrib module support in UUID module, then I'd suggest adding a new hook, so other modules could alter the information before uuid_get_core_entity_info() returns it. But this is beyond this very issue (as it belongs to the UUID queue anyway).

boobaa’s picture

Status: Needs work » Needs review
joao sausen’s picture

Boobaa, why does the patch applies to uuid_extras?

Also, confirming that #42 works!

harish.04’s picture

Hi Boobaa,

I have placed the "field_collection_uuid" module under UUID extras module and I have also applied the patch on UUID module as per #42 but still it does not support Field collections to be deployed.

Still I get the error "DeployServiceException: Service error: 404 Not found : Could not find resource field_collection_item."

Could you please help me with this issue.

Regards,
Harish M.

kt2ssh’s picture

#42 it does works

boobaa’s picture

@Joao Sausen in #44: the uuid_extras is the module to be patched since UUID folks decided to stop supporting non-core modules (see #40 and #41 above).

Adirael’s picture

Maybe it would work better as a submodule for field_collection, just an idea.

Anyway, patches on #42 worked great for me.