EmberJS 2.7 = has_many Ember-Data 和活动模型序列化程序的配置,使用 Ember-Power-Select (和侧面加载,而不是嵌入数据)

EmberJS 2.7 = has_many configuration for Ember-Data and Active Model Serializers, using Ember-Power-Select (and side loaded, not embedded data)

这是一个与 this one 类似的问题,不同之处在于这是针对 Ember 和 Active Model Serializers (0.10.2) 的最新版本。

我有一段简单的 Parent:Child 关系。

app/models/trail.js

import Ember from 'ember';
import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr(),

  // relationships
  employees: DS.hasMany('employee', { async: true }),

});

app/models/employee.js

import DS from 'ember-data';

import Person from '../models/person';

export default Person.extend({
  status: DS.attr(),
  statusCode: DS.attr(),
});

app/models/person.js

import Ember from 'ember';
import DS from 'ember-data';

export default DS.Model.extend({
  avatarUrl: DS.attr(),
  firstName: DS.attr(),
  lastName: DS.attr(),

  fullName: Ember.computed('firstName', 'lastName', function() {
    return `${this.get('lastName')}, ${this.get('firstName')}`;
  }),
});

当我创建一个新的 Trail,并且 select 两名员工为 'hasMany' 时,以下 json 到达 服务器(来自Rails 日志):

{"data":
  {"attributes":
    {"name":"TEST3", 
      "gpx-file-url":"a url", 
      "distance-value":"5"}, 
      "relationships":
         {"employees":{"data":[]}}, "type":"trails"}}

我的问题是,员工怎么了?员工的 ID 在哪里(他们已经存在于数据库和 Ember 商店中 - 即,我没有尝试在此请求中创建 child 记录)。

编辑

我刚发现 this 问题,它解释了 hasMany 关系的 ID 不是由 Ember 的 JSONAPISerializer 发送到 API - 因为这里的外键实际上必须保留在每个 child 记录中。因此,基本上 'selecting' 名员工,您需要保存他们现在拥有 parent 个事实。所以 selected 员工记录需要持久化。

但我的理解是这一切都有效 "out of the box" 并且 Ember 会自动触发 POST 请求来执行此操作,但情况似乎并非如此。

这就进入了真正的问题 - 我如何更新那些 children?


更新 - 由于这个问题已经发展,所以增加了赏金


经过进一步分析,很明显需要一个新模型 - Assignments。所以现在的问题比较复杂。

模型结构现在是这样的:


踪迹

有许多作业


员工

有许多作业


作业

属于步道

属于员工


在我的'new Trail'路线中,我使用奇妙的ember-power-select让用户select员工。单击 'save' 后,我计划遍历 selected 员工,然后创建分配记录(显然保存它们,在保存 Trail 本身之前或之后,不确定哪个是最好的)。

但是,问题仍然存在,我不知道该怎么做 - 如何找到 'selected' 员工,然后遍历他们以创建分配。

所以,这是我模板中的相关 EPS 用法:

在/app/templates/trails/new.hbs

  {{#power-select-multiple options=model.currentEmployees 
    searchPlaceholder="Type a name to search"
    searchField="fullName"
    selected=staff placeholder="Select team member(s)"
    onchange=(route-action 'staffSelected') as |employee| 
  }}
  <block here template to display various employee data, not just 'fullName'/>
  {{/power-select-multiple}}

(route-action 是来自 Dockyard 的助手,它会自动将动作发送到我的路线,效果很好)

这是我的模型:

  model: function () {
    let myFilter = {};
    myFilter.data = { filter: {status: [2,3] } }; // current employees
    return Ember.RSVP.hash({
      trail: this.store.createRecord('trail'),
      currentEmployees: this.store.query('employee', myFilter).then(function(data) {return data}),
    });
  },

  actions: {
    staffSelected (employee) {
      this.controller.get('staff').pushObject(employee);      
      console.log(this.controller.get('staff').length);
    },
  }

我今天才发现我们还需要控制器,所以这可能是我的问题!这是:

import Ember from 'ember';

export default Ember.Controller.extend({
  staff: [] <- I guess this needs to be something more complicated
});

这有效,我看到一个 object 被添加到控制台的数组中。但是 EPS 拒绝工作,因为我在控制台中收到此错误:

trekclient.js:91 Uncaught TypeError: Cannot read property 'toString' of undefined(anonymous function) @ trekclient.js:91ComputedPropertyPrototype.get @ vendor.js:29285get @ 

etc....

紧随其后的是:

vendor.js:16695 DEPRECATION: You modified (-join-classes (-normalize-class "concatenatedTriggerClasses" concatenatedTriggerClasses) "ember-view" "ember-basic-dropdown-trigger" (-normalize-class "inPlaceClass" inPlaceClass activeClass=undefined inactiveClass=undefined) (-normalize-class "hPositionClass" hPositionClass activeClass=undefined inactiveClass=undefined) (-normalize-class "vPositionClass" vPositionClass activeClass=undefined inactiveClass=undefined)) twice in a single render. This was unreliable in Ember 1.x and will be removed in Ember 3.0 [deprecation id: ember-views.render-double-modify]

所以我想这是因为文档中的示例只使用了一个包含字符串的数组,而不是实际的 Ember.Objects。但是我不知道如何解决这个问题。

所以,我决定扔掉控制器(哈哈),发挥创意。

如果我在 Trail 模型中添加一个 属性 会怎么样?这个 属性 基本上可以是 'dummy' 属性 收集了 selected 员工。

在/app/models/trail.js

selectedEmps: DS.hasMany('employee', async {false}) 

我将 async 设置为 false,因为我们不会保留它们,在保存新 Trail 之前,我可以再次将其设置为 null。

在/app/templates/trails/new.js

  {{#power-select-multiple options=model.currentEmployees 
    searchPlaceholder="Type a name to search"
    searchField="fullName"
    selected=model.selectedEmps placeholder="Select team member(s)"
    onchange=(action (mut model.selectedEmps)) as |employee| 
  }}
  <block here again/>
  {{/power-select-multiple}}

这行得通,但在 select 第一个员工之后 'blow up' 行不通。我可以 select 多个并从模板中删除它们。该控件似乎工作正常,因为它正在直接变异 'model.selectedEmps'。

现在,我认为这是一个 hack,因为我有两个问题:

  1. 如果我将 'mut' 更改为一个动作,这样我就可以添加更多的逻辑,我 无法弄清楚如何访问实际存储在 属性'model.selectedEmps'
  2. 即使我能弄清楚 (1) 我也必须始终确保 'selectedEmps'离开这条路线时清空,否则 下次进入这条路线时,它会记住之前的内容 select之前编辑过(因为它们现在在 Ember.Store)

根本问题是我可以接受 'mut' 但仍然有问题,当用户点击 'Save' 时,我必须弄清楚哪些员工被 selected,所以我可以为他们创建作业。

但我不知道如何访问 selected 的内容。也许这 Spaghetti-Monster-awful 一团糟:

    save: function (newObj) {
      console.log(newObj.get('selectedEmps'));
      if (newObj.get('isValid')) {
        let emp = this.get('store').createRecord('assignment', {
          trail: newObj,
          person: newObj.get('selectedEmps')[0]
        })
        newObj.save().then( function (newTrail) {
            emp.save();
            //newTrail.get('selectedEmps')
 //         this.transitionTo('trails');
          console.log('DONE');
        }); 
      }
      else {
        alert("Not valid - please provide a name and a GPX file.");
      }
    },

所以有两个问题需要解决:

  1. 如何获得 selected 员工,迭代并创建 作业。
  2. 如何将结果保存到API(JSON-API 使用Rails)。我 假设 newObj.saveeach assignment.save 会照顾 那个

更新


EPS 的开发者友善地指出动作处理程序接收一个数组,因为我改为使用多个 select,而不是像以前那样使用单个 select。因此,该操作正在接收当前 selected 的完整数组。卫生署!

因此,我能够按如下方式更新操作处理程序,它现在成功地将当前 selected 员工存储在控制器的员工 属性 中。更近一步。

  staffSelected(newList) {
      existing.forEach(function(me){
        if (!newList.includes(me)) {
          existing.removeObject(me); // if I exist but the newList doesn't have me, remove me
        }
      });
      newList.forEach(function(me){
        if (!existing.includes(me)) {
          existing.pushObject(me); // if I don't exist but the newList has me, add me
        }          
      });
  }

也许这不是将 2 个数组相交的最佳方法,但这是我周六晚上凌晨 4 点最不关心的问题。 :(


最后的问题更新 - 如何保存数据?


好的,现在我可以获得 selected 员工,我可以创建分配,但仍然无法弄清楚 Ember 我需要什么来保存他们,这个保存操作抛出一个错误:

save: function (newObject) {
      if (newObject.get('isValid')) {
        let theChosenOnes = this.controller.get('theChosenOnes');
        let _store = this.get('store');
        theChosenOnes.forEach(function (aChosenOne) {
          _store.createRecord('assignment', {
            trail: newObject,
            person: aChosenOne,
          });
        });
        newObject.save().then(function (newTrail) {
          newTrail.get('assignments').save().then(function() {
            console.log('DONE');
          });
        });
      }

get(...).save is not a function

最后一次更新的问题是,在 Ember 数据 2.x 中,关系默认是异步的,因此从 newTrail.get('assignments') 返回的不是 DS.ManyArray,其中有 .save,但 PromiseArray 没有。

您需要进行一些小的调整才能执行此操作,因此您在已解决的关系上调用 .save

newObject.save().then(function (newTrail) {
    newTrail.get('assignments').then(assignments => assignments.save()).then(function() {
        console.log('DONE');
    });
});