Monday, April 4, 2016

EmberJS, wait for session.user

EmberJS-д ember-simple-auth плагиныг хэрэглэн хэрэглэгчээр нэвтрэх үйлдлийг хэрэгжүүллээ гэж үзье.

import Ember from 'ember';
import ESASession from 'ember-simple-auth/services/session';

export default ESASession.extend({
  store: Ember.inject.service(),
  router: Ember.inject.service('-routing'),
  setCurrentUser: function() {
    var self = this;
    if (this.get('isAuthenticated')) {
      this.get('store').queryRecord('user', {
        'me': true
      }).then((user) => {
        this.set('currentUser', user);
        if (Ember.isEmpty(user.get('organization.id'))) {
          self.get('router').transitionTo('profile.organization');
        }
      });
    }
  }.observes('isAuthenticated')
});
Энэ кодонд isAuthenticated хувьсагч true болох үед өөрөөр хэлбэл хэрэглэгчээр нэвтэрч орох үед back-end рүү өөрийнхөө хэрэглэгчийн мэдээллийг авах GET - /users?me=true хүсэлт явуулж байна. Мэдээлэл ирсэн бол session.currentUser хувьсагчид хэрэглэгчийн мэдээлэл хадгалагдана.

Ember Simple Auth-ийн хувьд session гэдэг бол singleton объект бөгөөд програмын ажиллагааны турш ажилладаг урт амьдралтай объект юм. Singleton шинж чанартай тул програмын аль ч хэсэгт inject хийгээд хандан ашиглаж болно.

User моделийг нэг харая
import DS from 'ember-data';

export default DS.Model.extend({
  email        : DS.attr('string'),
  username     : DS.attr('string'),
  password     : DS.attr('string'),
  ...
  organization : DS.belongsTo('organization', {async: true})
});
Модель маань Organization гэсэн өөр модельтой belongsTo хамааралтай байна. Өөрөөр хэлбэл User бүр ядаж нэг Organization-д харъяалагдах ёстой.

За одоо блог постын үндсэн асуудлыг гаргаж тавъя.
Хэрэв хэрэглэгч дөнгөж бүртгүүлчихээд нэвтэрч орсон бол заавал байгууллагынхаа мэдээллийг тохируулах ёстой гэж үзье. Дээрхи session объектэд
if (Ember.isEmpty(user.get('organization.id'))) {
  self.get('router').transitionTo('profile.organization');
}
гэсэн код байгаа энэ нь юу гэсэн үг вэ гэхээр organization буюу байгууллагаа тохируулаагүй бол заавал profile/organization гэсэн route рүү шидэж байгууллагын мэдээллийг тохируулах газар аваачина гэсэн үг юм.
profile/organization гэсэн route-рүү ороход хэрэглэгч нэг бол өмнө нь байгууллагаа тохируулчихсан аль эсвэл шинээр Organization объект үүсгэн тохируулах ёстой байна. Тэгвэл энэ route-рүү ороход байгууллагын мэдээллийг тохируулсан эсэхийг шалгахын тулд session.currentUser.organization.id байгаа эсэхийг эхлээд мэдэх шаардлагатай гэсэн үг. Гэтэл currentUser объектэд route-рүү орсон дариуд хандахыг оролдвол session.currentUser хараахан set хийгдээгүй байх магадлалтай, currentUser хоосон бол мэдээж organization-ийг ч мөн адил шалгах боломжгүй юм. Яагаад вэ гэвэл
this.get('store').queryRecord('user', {
  'me': true
}).then((user) => {
  this.set('currentUser', user);
  if (Ember.isEmpty(user.get('organization.id'))) {
    self.get('router').transitionTo('profile.organization');
  }
});
гэсэн үйлдэл бол Promise-д суурилсан үйлдэл өөрөөр хэлбэл GET request явуулаад хариу ирсэний дараа л resolved болдог гэсэн үг. Сүлжээгээр дамжуулан хүсэлт явуулах нь тодорхой хугацаа шаарддаг тул currentUser нь null буюу хараахан утга оноогдоохгүй байх магадлалтай гэсэн үг. Тэгвэл үүнд утга олгогдтол нь хүлээж болох шийдэл юу байж болох вэ?

Үүнд хэрэг болох ember-concurrency гэдэг async task ажиллуулдаг ember-ийн нэмэлт байна. Үүнийг тэгвэл session объектийн currentUser-т утга оноогдтол нь хүлээлгэхээр ашиглая.
import Ember from 'ember';
import {
  task,
  timeout
} from 'ember-concurrency';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';

export default Ember.Route.extend(AuthenticatedRouteMixin, {
  session: Ember.inject.service(),
  model() {
    return null;
  },
  setupController(controller, model) {
    this.get('pollSessionForChanges').perform().then((organization) => {
      controller.set('model', organization);
    });
  },
  pollSessionForChanges: task(function*() {
    yield timeout(500);
    try {
      while (Ember.isEmpty(this.get('session.currentUser'))) {
        yield timeout(500);
      }
      var organizationId = this.get('session.currentUser.organization.id');
      if (Ember.isEmpty(organizationId)) {
        return this.store.createRecord('organization');
      } else {
        return this.store.findRecord('organization', organizationId);
      }
    } finally {}
  }).cancelOn('deactivate').restartable(),
  actions: {
    willTransition() {
      if (Ember.isEmpty(this.get('session.currentUser.organization.id'))) {
        swal("Байгууллагаа эхлээд тохируулна уу!");
        this.transitionTo('profile.organization');
      }
    }
  }
});
profile.organization route-рүү орох үед pollSessionForChanges гэдэг coroutine task ийг ажиллуулахаар дуудна. Энэ task нь өөр route-рүү орох үед cancel хийгдэж session.currentUser-т утга оногдтол нь хүлээнэ. Утга олгогдсон бол organization-ийг тохируулсан эсэхийг нь шалгаад хэрвээ тохируулагдаагүй байвал шинээр organization объект үүсгээд буцаад а харин тохируулагдчихсан байвал id-аар бичлэгийг нь хайж олоод буцаана.

model-д утга оногдтол буюу organization засварлах боломжтой болтол дараах байдлаар loader-ийг template дээрээ харуулж болно.
{{#if model}}
  {{user-components/organization 
    model    = model 
    doSave   = "configureOrganization"}}
{{else}}
  {{loader-components/circular-loader}}
{{/if}}