Angularjs: многие ко многим со сквозной связью

В моем приложении есть учащиеся, которые могут записаться на классы.

В регистрации записываются даты начала и окончания вместе со ссылкой на класс.

Класс содержит подробные сведения, такие как имя и описание класса.

Студент (1) - (N) Зачисление (1) - (1) Класс

Следовательно

Студент (1) - (N) класс

Вот как я хотел бы, чтобы объект выглядел.

{
  "studentId": 1,
  "name": "Henrietta",
  "enrolledClasses": [
    {
      "enrollmentId": 12,
      "startDate": "2015-08-06T17:43:14.000Z",
      "endDate": null,
      "class": 
        { 
          "classId": 15,
          "name": "Algebra",
          "description": "..."
        }
    },
      "enrollmentId": 13,
      "startDate": "2015-08-06T17:44:14.000Z",
      "endDate": null,
      "class": 
        {
          "classId": 29,
          "name": "Physics",
          "description": "..."
        }
    }
}

Но мой RESTful API не может (легко) выводить полную вложенную структуру отношений, потому что ORM не может выполнять отношения «многие ко многим» со сквозными отношениями. Таким образом, я получаю студента и его несколько зачислений, но только ссылку на класс для каждого, а не детали. И мне нужно показать детали, а не только идентификатор.

Я мог бы дождаться, пока объект учащегося станет доступным, а затем использовать ссылки, чтобы сделать дополнительные вызовы API для каждого classId, чтобы получить подробности, но я не уверен, как и даже если бы затем интегрировать его с объектом учащегося.

Это то, что у меня есть до сих пор.

function findOne() {
  vm.student = StudentsResource.get({
    studentId: $stateParams.studentId
  });
};

Это то, что я получаю от своего API

{
  "studentId": 1,
  "name": "Henrietta",
  "enrolledClasses": [
    {
      "enrollmentId": 12,
      "startDate": "2015-08-06T17:43:14.000Z",
      "endDate": null,
      "class": 15
    },
      "enrollmentId": 13,
      "startDate": "2015-08-06T17:44:14.000Z",
      "endDate": null,
      "class": 29
    }
}

А что касается деталей класса, я могу их по одному, но не понял, как дождаться, пока объект ученика будет доступен, чтобы втолкнуть их в него. И как их правильно толкать...

{ 
  "classId": 15,
  "name": "Algebra",
  "description": "..."
}

{
  "classId": 29,
  "name": "Physics",
  "description": "..."
}

Вопрос

Хорошо ли объединять сведения об учащемся и классе(ах) в зачислении в один объект?

  • Если да, то какой самый понятный способ сделать это?
  • Если нет, то какой другой эффективный подход?

Имейте в виду, что приложение позволяет вносить изменения в сведения об учащихся и зачислениях, но не должно позволять вносить изменения в сведения о названии или описании класса. Поэтому, если Angular должен был отправить PUT для объекта, он не должен пытаться отправить детали любого класса, а только ссылку. Это будет принудительно на стороне сервера для защиты данных, но во избежание ошибок клиент не должен пытаться.

Для фона я использую SailsJS и PostgreSQL для серверного API.


person shanemgrey    schedule 06.08.2015    source источник
comment
Сколько классов в системе? Можно ли получить их все одним запросом?   -  person Ori Drori    schedule 07.08.2015
comment
Многое зависит от того, что вам нужно делать с этим в приложении. Скорее всего, вам не нужно объединять данные, и вы можете запрашивать подробности по запросу. Или, как было предложено выше, заранее запустите все классы и выполняйте поиск по мере необходимости. Хэш-карта, использующая идентификатор класса в качестве ключей, сделает поиск очень простым. Если вы хотите, чтобы они были объединены, было бы несложно удалить детали в перехватчике при отправке обновлений на сервер.   -  person charlietfl    schedule 07.08.2015
comment
@OriDrori В случае этого примера любой студент будет записываться менее чем на 10 классов одновременно. Но с годами это число может увеличиться до менее чем 50.   -  person shanemgrey    schedule 07.08.2015
comment
Как сказал @charlietfl, я бы получил список всех классов при загрузке приложения, хешировал их и использовал по мере необходимости. Во всяком случае, вы не должны комбинировать их с объектом ученика для отображения информации.   -  person Ori Drori    schedule 07.08.2015
comment
@charlietfl Я ищу идеи, как сделать это чистым и понятным. Кажется небрежным вручную прикреплять объект для замены эталонного значения только для того, чтобы позже поймать и удалить его. Но также кажется небрежным не хранить все три уровня глубины в одном объекте, когда связь 1-ко-многим между Class и Enrollments ясна. Это M-to-M через ассоциацию кажется достаточно распространенной потребностью, чтобы существовал хорошо установленный паттерн. Я просто еще не нашел.   -  person shanemgrey    schedule 07.08.2015
comment
Я бы не стал заменять эталонное значение, если бы вы их объединили. Просто добавьте дополнительное свойство, которое легко удалить. Преобразования данных не являются чем-то необычным, но многое зависит от варианта использования.   -  person charlietfl    schedule 07.08.2015


Ответы (1)


Таким образом, в основном на стороне Sailsjs вы должны переопределить функцию findOne в вашем StudentController.js. Это при условии, что вы используете чертежи.

// StudentController.js
module.exports = {
  findOne: function(req, res) {
    Student.findOne(req.param('id')).populate('enrollments').then(function(student){
      var studentClasses = Class.find({
        id: _.pluck(student.enrollments, 'class')
      }).then(function (classes) {
        return classes
      })
      return [student, classes]
    }).spread(function(student, classes){
      var classes = _.indexBy(classes, 'id')
      student.enrollments = _.map(student.enrollments, function(enrollment){
        enrollment.class = classes[enrollment.class]
        return enrollment
      })
      res.json(200, student)
    }).catch(function(err){
      if (err) return res.serverError(err)
    })
  }
}

// Student.js

module.exports = { 
  attributes: {
    enrollments: {
      collection: 'enrollment',
      via: 'student'
    } 
    // Rest of the attributes..   
  }
}

// Enrollment.js

module.exports = {
  attributes: {
    student: {
      model: 'student'
    }
    class: {
      model: 'class'
    }
    // Rest of the attributes...
  }
}

// Class.js

module.exports: {
  attributes: {
    enrollments: {
      collection: 'enrollment',
      via: 'class'
    }
    // Rest of the attrubutes
  }
}

Объяснение:
1. Функция _.pluck с использованием Lo-dash повторно настраивает массив classId, и мы находим все классы, которые мы хотели заполнить. Затем мы используем цепочку обещаний .spread(), создаем массив, проиндексированный classid, и сопоставляем classIds с фактическими экземплярами классов, которые мы хотели заполнить в зачислениях для студента, возвращенных из Student.findOne().
2. Верните учащегося с зачислениями, заполненными соответствующим классом.

источник: Sails.js заполняет вложенные ассоциации

person willjleong    schedule 06.08.2015
comment
Для действия Read это прекрасно работает (после исправления .then). Но побочным эффектом является то, что когда клиент удаляет зачисление из записи об учащемся, запись о нем в базе данных не удаляется. Он потерян с нулевым значением для студента и по-прежнему отображается в списке зачислений для класса, но с нулевым значением studentId. Кажется, мне придется переопределить план обновления, чтобы справиться с этим. И теперь это снова кажется мне хакерским. В качестве другого подхода я рассматриваю использование модели зачисления в качестве собственного углового контроллера внутри контроллеров ученика и класса. - person shanemgrey; 13.08.2015
comment
Когда вы удаляете запись, почему бы вам не вызвать метод удаления /enrollment/:id вместо обновления записи Student. Это автоматически каскадно удаляет связанные регистрации в записи студента. Другое решение состоит в том, чтобы фактически не удалять, а иметь поле с именем isDeleted и сделать его ложным. Затем вам нужно переопределить все чертежи, чтобы всегда запрашивать isDeleted == true. К сожалению, в Sails все не так просто, как в rails. Как только вы напишете код один раз, его легко перепрофилировать. - person willjleong; 14.08.2015