Show:
  1. // eslint-disable-next-line ember/no-mixins
  2. /**
  3. orm model
  4. @module mixins
  5. @submodule model
  6. */
  7.  
  8. /**
  9.  
  10. @example
  11. import EmberObject from '@ember/object';
  12. import model, {DS} from 'ember-easy-orm/mixins/model'
  13.  
  14. const {attr} = DS;
  15.  
  16. export default EmberObject.extend(model, {
  17. url: '/v1/food',
  18. init(){
  19. this._super(...arguments);
  20. this.model = {
  21. 'name': attr('string'),
  22. 'desc': attr('string'),
  23. 'pic': attr('array'),
  24. 'province_id': attr('string'),
  25. 'city_id': attr('string'),
  26. 'area_id': attr('string'),
  27. 'town_id': attr('string'),
  28. 'country_id': attr('string'),
  29. 'url': attr('string'),
  30. 'host': attr('string'),
  31. 'tag': attr('array'),
  32. 'user': attr({defaultValue: function(){
  33. return {name: '', 'gender': ''};
  34. }})
  35. };
  36. }
  37. })
  38. */
  39.  
  40. import { A, isArray } from '@ember/array';
  41. import Mixin from '@ember/object/mixin';
  42. import Evented from '@ember/object/evented';
  43. import EmberObject, { computed } from '@ember/object';
  44. import { merge } from '@ember/polyfills';
  45. import { isBlank, isNone } from '@ember/utils';
  46.  
  47. import ajax from './ajax';
  48.  
  49. export const DS = {
  50. attr(type, hash) {
  51. if (typeof type === 'object') {
  52. hash = type;
  53. type = undefined;
  54. }
  55.  
  56. if (typeof hash === 'object') {
  57. if (hash.defaultValue !== undefined) {
  58. return hash.defaultValue;
  59. }
  60. }
  61.  
  62. switch (type) {
  63. case 'string':
  64. return '';
  65. case 'boolean':
  66. return true;
  67. case 'number':
  68. return 0;
  69. case 'array':
  70. return A;
  71. }
  72.  
  73. return null;
  74. },
  75. };
  76.  
  77. /**
  78. mixin in ORM model
  79. @public
  80. @class model
  81. **/
  82. export default Mixin.create(ajax, Evented, {
  83. /**
  84. The api host, default is current host
  85. @property {String} host
  86. @default ""
  87. */
  88. host: '',
  89.  
  90. /**
  91. The api namespace like /v1 /v2
  92. @property {String} namespace
  93. @default ""
  94. */
  95. namespace: '',
  96.  
  97. /**
  98. The response data business logic root key like: {'code': 0, 'resp':{'user':[]}, 'msg':''}, the resp is is the rootKey
  99. @property {String} rootKey
  100. @default ""
  101. */
  102. rootKey: '',
  103.  
  104. /**
  105. The api url. If rootURL ends with slash , the url should not starts with slash
  106. @property {String} url
  107. @default ""
  108. */
  109. url: '',
  110.  
  111. /**
  112. The model object primary key
  113. @property {String} primaryKey
  114. @default "_id"
  115. */
  116. primaryKey: '_id',
  117.  
  118. /**
  119. The object is for extract response data {user: [], comment:[], avatar: {}}
  120. @property {Object} displayModel
  121. @default null
  122. */
  123. displayModel: null,
  124.  
  125. /**
  126. url for find method request, use this method to custome find url
  127. @method urlForFind
  128. @default /host/namespace/?key=params[key]
  129. @return String
  130. */
  131. urlForFind: function () {
  132. return this.api;
  133. },
  134.  
  135. /**
  136. url for findOne method request, use this method to custome findOne url
  137. @method urlForFindOne
  138. @default /{host}/{namespace}/{id}
  139. @return String
  140. */
  141. urlForFindOne: function (id) {
  142. return this.api + '/' + id;
  143. },
  144. /**
  145. url for save method request, use this method to custome create and update url
  146. @method urlForSave
  147. @param id object primary key
  148. @default /{host}/{namespace}/{id}/
  149. @return String
  150. */
  151. urlForSave: function (id) {
  152. return id ? this.api + '/' + id : this.api;
  153. },
  154.  
  155. /**
  156. url for delete method request, use this method to custome delete url
  157. @method urlForDelete
  158. @default /{host}/{namespace}/{id}
  159. @param id object primary key
  160. @return String
  161. */
  162. urlForDelete: function (id) {
  163. return id ? this.api + '/' + id : this.api;
  164. },
  165. /**
  166. make api with host, namespace, url
  167. @method api
  168. @property {String} api
  169. @return {host}{namespace}{url}
  170. */
  171. api: computed('host', 'namespace', 'url', function () {
  172. return this.host + this.namespace + this.url;
  173. }),
  174. /**
  175. save the record to backend when create or update object
  176. @method save
  177. @param model model needed to save
  178. @return {Promise}
  179. */
  180. save: function (model) {
  181. let self = this,
  182. primaryKey = this.primaryKey,
  183. url = this.urlForSave(model[primaryKey], model),
  184. record = {},
  185. model_keys = Object.keys(this.model);
  186.  
  187. //filter model data
  188. for (var i = model_keys.length - 1; i >= 0; i--) {
  189. let key = model_keys[i];
  190. if (typeof self.model[key] === 'function') {
  191. if (typeof model[key] === 'object' && !isArray(model[key])) {
  192. record[key] = JSON.stringify(model[key]);
  193. continue;
  194. }
  195. }
  196.  
  197. if (isArray(model[key])) {
  198. let content = model[key];
  199. for (let i = 0; i < content.length; i++) {
  200. if (typeof content[i] === 'object' && content[i]) {
  201. model[key][i] = JSON.stringify(content[i]);
  202. }
  203. }
  204. }
  205.  
  206. record[key] = model[key] !== undefined ? model[key] : self.model[key];
  207. }
  208.  
  209. //check if is new data
  210. if (model[primaryKey]) {
  211. record[primaryKey] = model[primaryKey];
  212. return this.request.put(url, { data: record }).then(
  213. function (data) {
  214. // eslint-disable-next-line no-useless-catch
  215. try {
  216. return self.saveSerializer(data);
  217. } catch (e) {
  218. throw e;
  219. }
  220. },
  221. function (reason) {
  222. throw reason;
  223. }
  224. );
  225. }
  226.  
  227. return this.request.post(url, { data: record }).then(
  228. function (data) {
  229. // eslint-disable-next-line no-useless-catch
  230. try {
  231. return self.saveSerializer(data);
  232. } catch (e) {
  233. throw e;
  234. }
  235. },
  236. function (reason) {
  237. throw reason;
  238. }
  239. );
  240. },
  241.  
  242. /**
  243. create new model with init options and model property
  244. @method createRecord
  245. @param {Object} init init data
  246. @return Object current model
  247. */
  248. createRecord: function (init) {
  249. let record = EmberObject.create();
  250. Object.keys(this.model).map((key) => {
  251. if (typeof this.model[key] === 'function') {
  252. record.set(key, this.model[key].apply());
  253. } else {
  254. record.set(key, this.model[key]);
  255. }
  256. });
  257.  
  258. if (typeof init === 'object') {
  259. merge(record, init);
  260. }
  261.  
  262. return record;
  263. },
  264.  
  265. /**
  266. delete the record from backend
  267. @method deleteRecord
  268. @param {Object} model
  269. @param {Object} data passed to backend server as extra params
  270. @return Promise
  271. */
  272. deleteRecord: function (model, data) {
  273. let self = this,
  274. _id =
  275. typeof model === 'string' || typeof model === 'number'
  276. ? model
  277. : model[this.primaryKey],
  278. url = this.urlForDelete(_id, data),
  279. options = data ? { data: data } : {};
  280.  
  281. return this.request.delete(url, options).then(
  282. function (data) {
  283. // eslint-disable-next-line no-useless-catch
  284. try {
  285. return self.deleteSerializer(data);
  286. } catch (e) {
  287. throw e;
  288. }
  289. },
  290. function (reason) {
  291. throw reason;
  292. }
  293. );
  294. },
  295. /**
  296. find the records from backend according to params
  297. @method find
  298. @param {Object} params query params
  299. @return Promise
  300. */
  301. find: function (params) {
  302. let self = this,
  303. url = this.urlForFind(params),
  304. filterParams = this._filterParams(params),
  305. options = filterParams ? { data: filterParams } : {};
  306.  
  307. return this.request.get(url, options).then(
  308. function (data) {
  309. // eslint-disable-next-line no-useless-catch
  310. try {
  311. return self.findSerializer(data);
  312. } catch (e) {
  313. throw e;
  314. }
  315. },
  316. function (reason) {
  317. throw reason;
  318. }
  319. );
  320. },
  321.  
  322. /**
  323. find only one according to primary id
  324. @method findOne
  325. @param {String} id primary key
  326. @param {Object} data query parameter append to url
  327. @return Promise
  328. */
  329. findOne: function (id, data) {
  330. let url = this.urlForFindOne(id, data),
  331. self = this,
  332. options = data ? { data: data } : {};
  333.  
  334. return this.request.get(url, options).then(
  335. function (data) {
  336. // eslint-disable-next-line no-useless-catch
  337. try {
  338. return self.findOneSerializer(data);
  339. } catch (e) {
  340. throw e;
  341. }
  342. },
  343. function (reason) {
  344. throw reason;
  345. }
  346. );
  347. },
  348. /**
  349. filter request params
  350. @private
  351. @method _filterParams
  352. @param {Object} params
  353. @return Object filtered params
  354. */
  355. _filterParams: function (params) {
  356. if (!params) {
  357. return;
  358. }
  359. Object.keys(params).map((k) => {
  360. if (isBlank(params[k])) {
  361. delete params[k];
  362. }
  363. });
  364. return params;
  365. },
  366. /**
  367. find serializer
  368. @method findSerializer
  369. @param {Object} data response data from backend
  370. @return serializer data
  371. */
  372. findSerializer: function (data) {
  373. let result = {};
  374. if (this.displayModel) {
  375. let objectKeys = Object.keys(this.displayModel);
  376. for (let i = 0; i < objectKeys.length; i++) {
  377. let key = objectKeys[i];
  378. let keyAttr = this.displayModel[key];
  379. if (keyAttr === 'array') {
  380. result[key] = this.to_array(data[key]);
  381. continue;
  382. }
  383. if (keyAttr === 'object') {
  384. result[key] = this.to_object(data[key]);
  385. continue;
  386. }
  387. result[key] = data[key];
  388. }
  389. return result;
  390. }
  391.  
  392. //data is null or undefined
  393. if (isNone(data)) {
  394. console.warn('findSerializer response data is null');
  395. return this.to_array();
  396. }
  397.  
  398. // rootKey is empty
  399. if (!this.rootKey) {
  400. return this.to_array(data);
  401. }
  402.  
  403. // response data must be array
  404. if (!isArray(data[this.rootKey])) {
  405. console.warn(
  406. 'findSerializer parsedData is not array',
  407. data,
  408. this.rootKey
  409. );
  410. return this.to_array();
  411. }
  412.  
  413. return this.to_array(data[this.rootKey]);
  414. },
  415. /**
  416. serializer for findOne method
  417. @method findOneSerializer
  418. @param {Object} data response data from backend
  419. @return serializer data
  420. */
  421. findOneSerializer: function (data) {
  422. //data is null or undefined
  423. if (isNone(data)) {
  424. console.warn('findOneSerializer response data is null');
  425. return this.to_object();
  426. }
  427.  
  428. // rootKey is empty
  429. if (!this.rootKey) {
  430. return data;
  431. }
  432.  
  433. // parsedObject must be object
  434. if (isNone(data[this.rootKey])) {
  435. console.warn('findOneSerializer parsedObject is null');
  436. return this.to_object();
  437. }
  438.  
  439. return this.to_object(data[this.rootKey]);
  440. },
  441. to_array: function (data) {
  442. return A(data || []);
  443. },
  444. to_object: function (data) {
  445. return EmberObject.create(data || {});
  446. },
  447. /**
  448. serializer for save method
  449. @method saveSerializer
  450. @param {Object} data response data from backend
  451. @return serializer data
  452. */
  453. saveSerializer: function (data) {
  454. return data;
  455. },
  456. /**
  457. serializer for delete
  458. @method deleteSerializer
  459. @param {Object} data response data from backend
  460. @return serializer data
  461. */
  462. deleteSerializer: function (data) {
  463. return data;
  464. },
  465. init: function () {
  466. this._super(...arguments);
  467. if (typeof this.rootKey !== 'string') {
  468. throw new Error(`rootKey only allow string type, now is ${this.rootKey}`);
  469. }
  470. },
  471. });
  472.