ember-data-model-fragments icon indicating copy to clipboard operation
ember-data-model-fragments copied to clipboard

Polymorphic not working?

Open FredUK opened this issue 8 years ago • 2 comments

Hi, I've been trying to get polymorphic records to work for the past day with no success.

I believe I have followed the instructions correctly and I have searched the open and closed issues but still no go. They work fine to an extent. However when instantiating the object it always gives me the parent object properties only.

I have a section which has an array of custom fields. Those custom fields are polymorphic

models/section.js

export default Fragment.extend({
  name: attr('string'),
  fields: fragmentArray('custom-field', { polymorphic: true, typeKey: 'fieldTypeName', defaultValue: []})
});

model/custom-field.js

export default Fragment.extend({
  fieldTypeName: attr('string'), //this comes in as custom-field-textbox
  description: attr('string'),

model/custom-field-textbox.js

import CustomField from './custom-field';

export default CustomField .extend({
  maxCharacterLimit: attr('number'),
});

serializers/custom-field.js

  serialize(record, options) {
    let json = this._super(...arguments);

    if (record instanceof BookingFormFieldTextbox) {
      json.maxCharacterLimit = record.get('maxCharacterLimit');
    }

    return json;
  }
});

// serializers/custom-field-textbox.js

import BookingFormFieldSerializer from './booking-form-field';
export default BookingFormFieldSerializer;

The API sends:

MaxCharacterLimit":50,
"FieldTypeName":"custom-field-textbox",
"Description":"textbox limit 60"}]

After the normalize function is called what I have is something like:

{data: {
   attributes: {
     fieldTypeName: "custom-field-textbox",
     fieldType 6,
     identity: 1,
     description: '',
   },
  type: 'custom-field'
}

However then calling maxCharacterLimit returns null.

Should the type be custom-field or custom-field-textbox ?

Also when I use createFragment('custom-field-textbox'..) it works fine and I can see and use the property maxCharacterLimit. I then do a PUT to save it and the model in Ember still shows the property. However when I refresh so that it reloads the data from the server it instantiates the custom-field object but no properties of the custom-field-textbox are seen.

Any ideas why this is not working?

Thanks in advance.

FredUK avatar Feb 21 '17 08:02 FredUK

I might have figured out why this is happening, The serializer for custom-field is being called:

normalize(type, payload, prop) {
  return this._super(...arguments);
}

The type is always myapp@model:custom-field even when the typeKey value is 'custom-field-textbox'. Not sure if that's intended. I also noticed that custom-field-textbox serializer is never called:

import JSONSerializer from 'ember-data/serializers/json';
export default JSONSerializer.extend({
  normalize(type, payload, prop) { // Never gets called
    let results = this._super(type, payload, prop);
    return results;
  }
});

So what I had to do was in the custom-field serializer check for the fieldTypeName and set the proper factory with:

//serializers/custom-field.js
normalize(type, payload, prop) {
    type = this.store.modelFactoryFor(payload.fieldTypeName);
    let results = this._super(type, newPayload, prop);
    return results;
}

My custom-field-textbox properties now work together with the parents properties.

Not sure if this is the way it should work but I hope it helps someone.

FredUK avatar Feb 21 '17 10:02 FredUK

I have just run into the same issue, I think.

I'm using a dynamic fragment with a set of polymorphic child fragments. Each child fragment has a defaultValue object set with the according typeKey property.

  • If the adapter can't find any data, everything is working fine, because of the correct defaultValue.
  • Saving the data with a custom serialize method which is checking the snapshot.record instance and applying the typeKey property afterwards, like described in the readme, is also working fine.
  • Loading from a valid adapter response does not work, because the data[ typeKey ] value seems to be missing in the fragment's getActualFragmentType method.

The reason for this is the JSONSerializer, which is stripping off the typeKey property from the data in its extractAttributes method. This has fixed the issue for me:

// child-serializer.js

import JSONSerializer from "ember-data/serializers/json";
import typeKey from "child-fragment-typekey";
import children from "all-of-my-child-fragments";

const { hasOwnProperty } = {};

export default JSONSerializer.extend({
  /**
   * @param {Snapshot} snapshot
   * @param {Model} snapshot.record
   * @returns {Object}
   */
  serialize({ record }) {
    const json = this._super( ...arguments );

    for ( const [ name, child ] of children ) {
      if ( record instanceof child ) {
        json[ typeKey ] = `child-${name}`;
        break;
      }
    }

    return json;
  },

  /**
   * Fix removal of the `typeKey` property
   * @param {Model} modelClass
   * @param {Object} [data]
   * @returns {Object}
   */
  extractAttributes( modelClass, data ) {
    const attributes = this._super( ...arguments );
    if ( data && hasOwnProperty.call( data, typeKey ) ) {
      attributes[ typeKey ] = data[ typeKey ];
    }

    return attributes;
  }
});

bastimeyer avatar Nov 06 '17 20:11 bastimeyer