文档章节

模型高级特性,引入模型关联关系

ubuntuvim
 ubuntuvim
发布于 2016/04/27 18:08
字数 2318
阅读 35
收藏 1

博文原址:模型高级特性,引入模型关联关系

接着前面五篇:

  1. 环境搭建以及使用Ember.js创建第一个静态页面
  2. 引入计算属性、action、动态内容
  3. 模型,保存数据到数据库
  4. 发布项目,加入CRUD功能
  5. 从服务器获取数据,引入组件

前言

本篇主要是介绍模型直接的关联关系,比如:一对一、一对多关系。会创建两个模型authorbook,设置它们的关系,并增加测试数据。

创建模型并设置关联

关联关系设置API:

  1. belongsTo
  2. hasMany

模型关系:一个library对应多个book,一个author对应多个book。关系图如下:

library模型关系图

使用Ember CLI命令创建模型。

ember g model book title:string releaseYear:date library:belongsTo author:belongsTo
ember g model author name:string books:hasMany

手动在library中增加hasMany关联关系。

import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { hasMany } from 'ember-data/relationships';
import Ember from 'ember';

export default Model.extend({
  name: attr('string'),
  address: attr('string'),
  phone: attr('string'),

  books: hasMany('books'),

  isValid: Ember.computed.notEmpty('name'),
});

创建一个后台管理页面“Seeder”

ember g route admin/seeder

检查router.js看看路由是否成功创建。相关代码如下:

//  其他代码不变,省略
this.route('admin', function() {
    this.route('invitations');
    this.route('contacts');
    this.route('seeder');
});
//  其他代码不变,省略

修改导航模板navbar.hbs增加新建路由的入口链接。

<ul class="dropdown-menu">
  {{#nav-link-to 'admin.invitations'}}Invitations{{/nav-link-to}}
  {{#nav-link-to 'admin.contacts'}}Contacts{{/nav-link-to}}
  {{#nav-link-to 'admin.seeder'}}Seeder{{/nav-link-to}}
</ul>

使用Ember.RSVP.hash()在一个路由中返回多个模型的数据

Ember支持在一个路由的model回调中返回多个模型的数据。有关方法发API请看Ember.RSVP.hash()

// app/routes/admin/seeder.js
import Ember from 'ember';

export default Ember.Route.extend({

  model() {
    return Ember.RSVP.hash({
      libraries: this.store.findAll('library'),
      books: this.store.findAll('book'),
      authors: this.store.findAll('author')
    })
  },

  setupController(controller, model) {
    controller.set('libraries', model.libraries);
    controller.set('books', model.books);
    controller.set('authors', model.authors);
  }
});

上述model()回调中返回了三个模型的数据:librarybookauthor。需要注意的是:上述代码中方法Ember.RSVP.hash()会发送3个请求,并且只有三个请求都成功才会执行成功。 在setupController()回调中,把三个模型分别设置到controller中。

路由内置方法调用次序

每个路由内都内置了很多方法,比如前面介绍的modelsetupControllerrenderTemplate,这些都是内置在路由类中的方法,那么这些方法调用次序又是如何的呢?请看下面的代码:

// app/routes/test.js

import Ember from 'ember';

export default Ember.Route.extend({

  init() {
    debugger;
  },

  beforeModel(transition) {
    debugger;
  },

  model(params, transition) {
    debugger;
  },

  afterModel(model, transition) {
    debugger;
  },

  activate() {
    debugger;
  },

  setupController(controller, model) {
    debugger;
  },

  renderTemplate(controller, model) {
    debugger;
  }
});

打开浏览器的debug模式并在执行到这个路由中http://localhost:4200/test。可以看到方法的执行次序与上述代码方法的次序是一致的。有关API请看下面网址的介绍:

  1. init()
  2. beforeModel(transition)
  3. model(params, transition)
  4. activate()
  5. setupController(controller, model)
  6. renderTemplate(controller, model)

数量显示功能

创建一个组件用于显示各个模型数据的总数。

ember g component number-box

组件创建完毕之后在组件类中增加css类,使用属性classNames设置。

// app/components/number-box.js
import Ember from 'ember';

export default Ember.Component.extend({

  classNames: ['panel', 'panel-warning']

});

然后在组件模板中增加代码:

<!-- app/templates/components/number-box.hbs -->
<div class="panel-heading">
  <h3 class="text-center">{{title}}</h3>
  <h1 class="text-center">{{if number number '...'}}</h1>
</div>

在修改app/templates/admin/seeder.hbs

<!-- app/templates/admin/seeder.hbs -->
<h1>Seeder, our Data Center</h1>

<div class="row">
  <div class="col-md-4">{{number-box title="Libraries" number=libraries.length}}</div>
  <div class="col-md-4">{{number-box title="Authors" number=authors.length}}</div>
  <div class="col-md-4">{{number-box title="Books" number=books.length}}</div>
</div>

等待项目重启完成,进入到后台的seeder下可以看到三个小圆点,请记得,一定要在setupController中设置数据,model回调会自动从服务器获取数据,obj.length意思是调用length()方法获取数据长度,然后直接显示到模板上,效果如下截图,由于后面两个模型还没有数据所以显示省略号。

结果截图

构建表单生成测试数据

前面已经介绍过属性的传递,下面的代码将为读者介绍一些更加高级的东西!!一大波代码即将来临!!!

ember g component seeder-block
ember g component fader-label
// app/components/seeder-block.js
import Ember from 'ember';

export default Ember.Component.extend({

  actions: {
    generateAction() {
      this.sendAction('generateAction');
    },

    deleteAction() {
      this.sendAction('deleteAction');
    }
  }
});
<!-- app/templates/components/seeder-block.hbs -->
<div class="row">
  <div class="col-md-12">
    <h3>{{sectionTitle}}</h3>

    <div class="row">
      <div class="form-horizontal">
        <label class="col-sm-2 control-label">Number of new records:</label>
        <div class="col-sm-2">
          {{input value=counter class='form-control'}}
        </div>
        <div class="col-sm-4">
          <button class="btn btn-primary" {{action 'generateAction'}}>Generate {{sectionTitle}}</button>
          {{#fader-label isShowing=createReady}}Created!{{/fader-label}}
        </div>
        <div class="col-sm-4">
          <button class="btn btn-danger" {{action 'deleteAction'}}>Delete All {{sectionTitle}}</button>
          {{#fader-label isShowing=deleteReady}}Deleted!{{/fader-label}}
        </div>
      </div>

    </div>
  </div>
</div>
// app/components/fader-label.js
import Ember from 'ember';

export default Ember.Component.extend({
  tagName: 'span',

  classNames: ['label label-success label-fade'],
  classNameBindings: ['isShowing:label-show'],

  isShowing: false,

  isShowingChanged: Ember.observer('isShowing', function() {
    Ember.run.later(() => {
      this.set('isShowing', false);
    }, 3000);
  })
});

代码classNames: ['label label-success label-fade']的作用是绑定三个CSS类到标签span上,得到html如<span class="label label-success label-fade">xxx</span>。 代码classNameBindings: ['isShowing:label-show']的作用是根据属性isShowing的值判断是否添加CSS类label-show到标签span上。更多有关信息请看Ember.js 入门指南之十二handlebars属性绑定

<!-- app/templates/components/fader-label.hbs -->
{{yield}}
// app/styles/app.scss
@import 'bootstrap';

body {
  padding-top: 20px;
}

html {
  overflow-y: scroll;
}

.library-item {
  min-height: 150px;
}

.label-fade {
  opacity: 0;
  @include transition(all 0.5s);
  &.label-show {
    opacity: 1;
  }
}

最主要、最关键的部分来了。

<!-- app/templates/admin/seeder.hbs -->
<h1>Seeder, our Data Center</h1>

<div class="row">
  <div class="col-md-4">{{number-box title="Libraries" number=libraries.length}}</div>
  <div class="col-md-4">{{number-box title="Authors" number=authors.length}}</div>
  <div class="col-md-4">{{number-box title="Books" number=books.length}}</div>
</div>

{{seeder-block
    sectionTitle='Libraries'
    counter=librariesCounter
    generateAction='generateLibraries'
    deleteAction='deleteLibraries'
    createReady=libDone
    deleteReady=libDelDone
}}

{{seeder-block
  sectionTitle='Authors with Books'
  counter=authorCounter
  generateAction='generateBooksAndAuthors'
  deleteAction='deleteBooksAndAuthors'
  createReady=authDone
  deleteReady=authDelDone
}}

属性generateActiondeleteAction用于关联控制器中的action方法,属性createReadydeleteReady是标记属性。

等待项目重启完毕,页面结果如下:

result

底部的两个输入框用于获取生成的数据条数。

安装faker.js构建测试数据

使用faker.js构建测试数据。

ember install ember-faker

安装完毕之后扩展各个模型,并在模型中调用randomize()方法产生数据。下面是各个模型的代码。

// app/models/library.js
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { hasMany } from 'ember-data/relationships';
import Ember from 'ember';
import Faker from 'faker';

export default Model.extend({
  name: attr('string'),
  address: attr('string'),
  phone: attr('string'),

  books: hasMany('book', {inverse: 'library', async: true}),

  isValid: Ember.computed.notEmpty('name'),

  randomize() {
    this.set('name', Faker.company.companyName() + ' Library');
    this.set('address', this._fullAddress());
    this.set('phone', Faker.phone.phoneNumber());

    // If you would like to use in chain.
    return this;
  },

  _fullAddress() {
    return `${Faker.address.streetAddress()}, ${Faker.address.city()}`;
  }
});
// app/models/book.js
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { belongsTo } from 'ember-data/relationships';
import Faker from 'faker';

export default Model.extend({

  title:        attr('string'),
  releaseYear:  attr('date'),

  author:       belongsTo('author', {inverse: 'books', async: true}),
  library:      belongsTo('library', {inverse: 'books', async: true}),

  randomize(author, library) {
    this.set('title', this._bookTitle());
    this.set('author', author);
    this.set('releaseYear', this._randomYear());
    this.set('library', library);

    return this;
  },

  _bookTitle() {
    return `${Faker.commerce.productName()} Cookbook`;
  },

  _randomYear() {
    return new Date(this._getRandomArbitrary(1900, 2015));
  },

  _getRandomArbitrary(min, max) {
    return Math.random() * (max - min) + min;
  }
});
// app/models/author.js
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { hasMany } from 'ember-data/relationships';
import Faker from 'faker';

export default Model.extend({

  name: attr('string'),

  books: hasMany('book', {inverse: 'author', async: true}),

  randomize() {
    this.set('name', Faker.name.findName());
    return this;
  }

});

上述代码中。 async设置为true的作用是:在获取book的同时会把关联的author也加载出来,默认是不加载(延迟加载)。

// app/controllers/admin/seeder.js
import Ember from 'ember';
import Faker from 'faker';

export default Ember.Controller.extend({

  libraries: [],
  books: [],
  authors: [],

  actions: {

    generateLibraries() {
      const counter = parseInt(this.get('librariesCounter'));

      for (let i = 0; i < counter; i++) {
        this.store.createRecord('library').randomize().save().then(() => {
          if (i === counter-1) {
            this.set('librariesCounter', 0);
            this.set('libDone', true);
          }
        });
      }
    },

    deleteLibraries() {
      this._destroyAll(this.get('libraries'));

      this.set('libDelDone', true);
    },

    generateBooksAndAuthors() {
      const counter = parseInt(this.get('authorCounter'));

      for (let i = 0; i < counter; i++) {
        let newAuthor = this.store.createRecord('author');
        newAuthor.randomize()
          .save().then(() => {
             if (i === counter-1) {
               this.set('authorCounter', 0);
               this.set('authDone', true);
             }
          }
        );

        this._generateSomeBooks(newAuthor);
      }
    },

    deleteBooksAndAuthors() {
      this._destroyAll(this.get('books'));
      this._destroyAll(this.get('authors'));

      this.set('authDelDone', true);
    }
  },

  // Private methods

  _generateSomeBooks(author) {
    const bookCounter = Faker.random.number(10);

    for (let j = 0; j < bookCounter; j++) {
      const library = this._selectRandomLibrary();
      this.store.createRecord('book')
        .randomize(author, library)
        .save();
      author.save();
      library.save();
    }
  },

  _selectRandomLibrary() {
    const libraries = this.get('libraries');
    const librariesCounter = libraries.get('length');

    // Create a new array from IDs
    const libraryIds = libraries.map((lib) => {return lib.get('id');});
    const randomNumber = Faker.random.number(librariesCounter-1);

    const randomLibrary = libraries.findBy('id', libraryIds[randomNumber]);
    return randomLibrary;
  },

  _destroyAll(records) {
    records.forEach((item) => {
      item.destroyRecord();
    });
  }

});

重启项目,进入到http://localhost:4200/admin/seeder。在输入框内输入要生成的测试数据条数,然后点击右边的蓝色按钮,如果生成成功可以在按钮右边看到绿色的“created”提示文字。如下图:

生成成功

然后到firebase上查看。可以看到数据已经存在了,并且是随机的数据。

firebase数据截图1

firebase数据截图2

并且是实现了数据表之间的关联关系,比如一个author对应多个book,如下图。

author一对多book

或者是直接在http://localhost:4200/libraries下查看。

在接下来的一篇文章中将介绍如何遍历关联关系中的对象,使用起来也是非常简单的,直接使用面向对象的方式遍历即可。

家庭作业

本篇的家庭作业仍然是好好理解组件!参照下面的文章认真学习、理解组件。

  1. Ember.js 入门指南之二十八组件定义
  2. Ember.js 入门指南之二十九属性传递
  3. Ember.js 入门指南之三十包裹内容
  4. Ember.js 入门指南之三十一自定义包裹组件的HTML标签
  5. Ember.js 入门指南之三十二处理事件
  6. Ember.js 入门指南之三十三action触发变化

<br> 为了照顾懒人我把完整的代码放在[GitHub](https://github.com/ubuntuvim/library-app)上,如有需要请可以拿来参照参照。博文经过多次修改,博文上的代码与github代码可能有出入,不过影响不大!如果你觉得博文对你有点用,请在github项目上给我点个`star`吧。您的肯定对我来说是最大的动力!!

© 著作权归作者所有

ubuntuvim
粉丝 33
博文 76
码字总数 98477
作品 1
深圳
后端工程师
私信 提问
精简CNN模型系列之七:Xception

介绍 Xception是Google出品,属于2017年左右的东东。它在Google家的MobileNet v1之后,MobileNet v2之前。 它的主旨与MobileNet系列很像即推动Depthwise Conv + Pointwise Conv的使用。只是它...

manofmountain
2018/10/20
0
0
ThinkPHP5.0.0 RC1版本发布——为API开发而设计

ThinkPHP V5.0——为API开发而设计的高性能框架 经过一段时间的测试和完善,官方正式发布第一个RC版本,该版本功能趋于稳定。 ThinkPHP5.0版本是一个颠覆和重构版本,基于PHP5.4设计(支持P...

流年
2016/01/30
2.6K
21
3、深入理解 Laravel Eloquent(三)——模型间关系(关联)

深入理解 Laravel Eloquent(三)——模型间关系(关联) 在本篇文章中,我将跟大家一起学习 Eloquent 中最复杂也是最难理解的部分——模型间关系。官方英文文档中叫 Relationships,个人认为...

我爱祥子
2016/01/10
61
0
UML 10 种常见的域建模错误

uml 这段时间在学 UML 建模,UML 10 中常见的域建模错误摘录如下: 1.立即关联指定多重度,确保每个关联都有明确的多重度 类图上有些关联度表示的是一对一的关系,而其他关联表示的是一对多的...

peiquan
2018/07/04
0
0
国际信息流短视频FM+GBM排序模型

背景 国际信息流短视频从图文拆分代码,独立部署后,沿用图文Wide&Deep排序模型。通过引入时长特征、点击+时长多目标优化等工作,我们取得了不错的收益: 增加视频平均播放时长特征,作为用户...

马泽锋
2018/08/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
今天
6
0
数据库中间件MyCat

什么是MyCat? 查看官网的介绍是这样说的 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务、ACID、可以替代MySQL的加强版数据库 一个可以视为MySQL集群的企业级数据库,用来替代昂贵...

沉浮_
今天
4
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
今天
6
0
常用物流快递单号查询接口种类及对接方法

目前快递查询接口有两种方式可以对接,一是和顺丰、圆通、中通、天天、韵达、德邦这些快递公司一一对接接口,二是和快递鸟这样第三方集成接口一次性对接多家常用快递。第一种耗费时间长,但是...

程序的小猿
今天
7
0
Python机器学习之数据探索可视化库yellowbrick

背景介绍 从学sklearn时,除了算法的坎要过,还得学习matplotlib可视化,对我的实践应用而言,可视化更重要一些,然而matplotlib的易用性和美观性确实不敢恭维。陆续使用过plotly、seaborn,...

yeayee
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部