Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot get attachments working (error in serializer) #229

Open
bastientanesie opened this issue Sep 3, 2018 · 9 comments
Open

Cannot get attachments working (error in serializer) #229

bastientanesie opened this issue Sep 3, 2018 · 9 comments

Comments

@bastientanesie
Copy link

bastientanesie commented Sep 3, 2018

Hi there!

I'm having a hard time working with ember-pouch (v6.0.0) and attachments on a ember-cli v3.3 app.

According to the README, I've setup my model like this (stripped down) gist.

The user selects a file with the <input type="file">, I get the File object in the controller and then add it to the User model as the README says (here as a Blob, but I also tried the base-64 way).

That throws me an error in the Serializer: Uncaught TypeError: Cannot convert undefined or null to object.

extractAttributes(modelClass, resourceHash) {
  let attributes = this._super(modelClass, resourceHash);
  let modelAttrs = get(modelClass, 'attributes');
  modelClass.eachTransformedAttribute(key => {
    let attribute = modelAttrs.get(key);
    if (this._isAttachment(attribute)) {
      let fileNames = keys(attributes[key]); // <----------- it breaks here
      fileNames.forEach(fileName => {
        attributes[key][fileName] = resourceHash.attachments[fileName];
      });
    }
  });
  return attributes;
},

When the error throws, fileNames is undefined, attributes looks like this:

{
  email: "[email protected]",
  username:  "user1",
  avatar: []
}

In CouchDB, the user's data look like that:

{
  "_id": "user_2_XXXXXXXX",
  "_rev": "X-XXXXX",
  "data": {
    "email": "[email protected]",
    "username": "bastien2",
    "avatar": []
  }
}

I've added the avatar field mysel, but it don't work without it anyway.

Everything looks ok, so I don't get why it breaks there. And I was unable to find any kind of help about that so here I am...

Let me know if you need more info!

@jlami
Copy link
Collaborator

jlami commented Sep 4, 2018

Could we see your model file? At first glance it looks like you have attachment there (as stated in the gist), where ember-pouch expects attachments (found in the readme). But I'm not sure, have not worked with them myself yet.

@bastientanesie
Copy link
Author

@jlami the model is available in the gist (file called model.js).
I discovered that ember-pouch comes with two transforms: attachment and attachments. As I wish the users to have only one avatar at the same time, I picked the single attachment transform.
I tried them both obviously, but none of them seems to solve my issue 😢.

@jlami
Copy link
Collaborator

jlami commented Sep 4, 2018

I think there might be a bug in the attachment transform. But only when you have not set the attachment, when it is null.
And I think your defaultValue for attachment needs to be null, not []. That value is only for the attachments

The following should work with null for the empty state:

export default AttachmentsTransform.extend({
  deserialize: function(serialized) {
    let atts = this._super(serialized);
    
    if (atts.length > 0)
      return atts.pop();
    else
      return null;
  },
  serialize: function(deserialized) {
    if (isNone(deserialized)) { return {}; }
    return this._super([deserialized]);
  }
});

I will see if I can make a PR for this.

I have only tested this with a 'local' pouchDB, not a couch database. I think your deserialize problem stems from wrong initial data in couch. The 'avatar' value should not be set as far as I know. It should only be on the _attachments. But I will see if I can test this with a couch db to verify.

---edit

This is the document I get when I use a couchdb:

{
  "_id": "test_2_D7D6EBC5-69D3-40E8-921B-2371CC2B6A1A",
  "_rev": "1-b90a885d30cce03c5c65cd598f7dbbc9",
  "data": {
    "email": null,
    "username": "test",
    "avatar": {
      "README.md": {}
    }
  },
  "_attachments": {
    "README.md": {
      "content_type": "",
      "revpos": 1,
      "digest": "md5-7oxNNt+ElDCjeUU33IFVYA==",
      "length": 1454,
      "stub": true
    }
  }
}

BTW when using the transform with the s, you have to do model.set('avatar', fileOrBlob), not the addObject, as you are working with only one value.

--edit2
Looking at your couchdb data, I think the problem is that you have [] in the database, not null (but without my PR it would not have worked anyway). I believe the serializer does not work with arrays, but with an object like in my example above. {} might also have worked with attachments.

@bastientanesie
Copy link
Author

bastientanesie commented Sep 5, 2018

Ok so I created a new transform in my ember app to test @jlami's solution (thanks BTW!) like so:

// /app/transforms/attachment.js
import { isNone } from '@ember/utils';
import { AttachmentsTransform } from 'ember-pouch';

export default AttachmentsTransform.extend({
  deserialize: function(serialized) {
    let atts = this._super(serialized);
    console.log('deserialize', atts);
    
    if (atts.length > 0)
      return atts.pop();
    else
      return null;
  },
  serialize: function(deserialized) {
    if (isNone(deserialized)) { return {}; }
    return this._super([deserialized]);
  }
});

In CouchDB, my doc looks like that:

{
  "_id": "user_2_XXX",
  "_rev": "1-XXX",
  "data": {
    "email": "[email protected]",
    "username": "user2",
    "avatar": null
  }
}

In my controller, I do this:

// /app/controllers/user.js
actions: {
  test() {
    let avatarDOMElement = document.getElementById('user-avatar-input');
    if (avatarDOMElement.files.length > 0) {
      let file = avatarDOMElement.files[0];
      this.set('model.avatar', file);
      this.get('model').save();
    }
  }
}

But at runtime, I still get the same errors as before, down to the serializer. Am I doing it wrong?
image

Edit:
I forgot to mention that I edited my User model with the default value to null.

// /app/models/user.js
avatar: DS.attr('attachment', {
  defaultValue: function() {
    return null;
  }
}),

@jlami
Copy link
Collaborator

jlami commented Sep 5, 2018

I haven't exactly looked at what couch will store if you set it to null. I'll try that later today.

But maybe it is an idea to see how a new model is saved if you do set a file? So don't load anything in the route model() and just try something like this?

// /app/controllers/user.js
//import {inject} from '@ember/service';
store: inject(),
actions: {
  test() {
    let avatarDOMElement = document.getElementById('user-avatar-input');
    if (avatarDOMElement.files.length > 0) {
      let file = avatarDOMElement.files[0];
      let model = this.store.createRecord('user', {username: 'user2', email: '[email protected]'});
      model.set('avatar', file);//also try without this line to see null result
      model.save();
    }
  }
}

That way you would not run into deserialize problems of the existing data.

@bastientanesie
Copy link
Author

bastientanesie commented Sep 5, 2018

Thank's to your idea @jlami, I spotted an error in my custom attachment transform.
The imported AttachmentsTransform path was wrong, I've updated it to import AttachmentsTransform from 'ember-pouch/transforms/attachment';.

Inside the new attachment transform (src) let atts = this._super(serialized); returns undefined, so if (atts.length > 0) fails: "Cannot read property 'length' of undefined".
Besides, should we use the default value provided (2nd arg of deserialize()) rather than null? Just wondering 😉.

Edit
I tried like you said, and the serializer seems to fail as usual. The model's JSON looks like that:
image

And the payload of the PUT request is this:
image

Obviously, the request fails with a "Bad request: Invalid attachment data for undefined".

@jlami
Copy link
Collaborator

jlami commented Sep 5, 2018

I think you might need to import the attachments version? Otherwise your _super will work with the attachment version, and not the version that returns arrays and such.
Another way is to just use jlami/ember-pouch#single-attachment-fix as the package src

I didn't know deserialize got the default value, might be a good idea to use that yes. But I would expect ember-data to set that internally if we return something empty? I would have to look into that.

@bastientanesie
Copy link
Author

bastientanesie commented Sep 5, 2018

Argh, sorry I messed it! Thanks.

So, I tried without setting the attachment and I get "avatar": {} in Couch, which seems to be ok (I think you're right about the default value).
Then I triend model.set() with a File object but it doesn't work. I have to set a Ember.Object for it to work (deserialize() do that as well) like so:

model.set('avatar', Ember.Object.create({
  name: file.name,
  content_type: file.type,
  data: file
}));

Now the document inside CouchDB is correct and I'm able to retrieve stub data of that attachment:

{
  "_id": "user_2_XXX",
  "_rev": "1-XXX",
  "data": {
    "email": "[email protected]",
    "username": "user",
    "avatar": {
      "ember.png": {}
    }
  },
  "_attachments": {
    "ember.png": {
      "content_type": "image/png",
      "revpos": 1,
      "digest": "md5-uIVjw2LSzOiPMhbakNbwCQ==",
      "length": 103382,
      "stub": true
    }
  }
}

Now I have to figure out how to get the non-stubbed attachment's data, but it seems others tried it some time ago 🤔.

Anyway, thank you very much @jlami, you saved me (and my customer OFC 😂)! Maybe we should update the README according to what we found?


Edit: attachments transform seems to be working too now!

I tried something like that and it work as expected 👍.

model.get('documents').addObject(EmberObject.create({
  name: file.name,
  content_type: file.type,
  data: file
}));

@jlami
Copy link
Collaborator

jlami commented Sep 5, 2018

Good to hear that you got it working. But I think you are right that getting the actual file back needs some more work. Don't know if there is a PR for that yet, but you can indeed look at the issue you mentioned to get the code you need in your own adapter for now.

Let's see what will have to be changed in ember-pouch to make this easier, and if you have any suggestions for the README, those are more than welcome 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants