资讯专栏INFORMATION COLUMN

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

raise_yang / 2294人阅读

摘要:创建模型并设置关联关联关系设置模型关系一个对应多个,一个对应多个。手动在中增加关联关系。并且是实现了数据表之间的关联关系,比如一个对应多个,如下图。

文章来源:模型高级特性,引入模型关联关系

接着前面五篇:

环境搭建以及使用Ember.js创建第一个静态页面

引入计算属性、action、动态内容

模型,保存数据到数据库

发布项目,加入CRUD功能

从服务器获取数据,引入组件

前言

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

创建模型并设置关联

关联关系设置API:

belongsTo

hasMany

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

使用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增加新建路由的入口链接。

使用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请看下面网址的介绍:

init()

beforeModel(transition)

model(params, transition)

activate()

setupController(controller, model)

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"]

});

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


{{title}}

{{if number number "..."}}

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


Seeder, our Data Center

{{number-box title="Libraries" number=libraries.length}}
{{number-box title="Authors" number=authors.length}}
{{number-box title="Books" number=books.length}}

等待项目重启完成,进入到后台的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");
    }
  }
});

{{sectionTitle}}

{{input value=counter class="form-control"}}
{{#fader-label isShowing=createReady}}Created!{{/fader-label}}
{{#fader-label isShowing=deleteReady}}Deleted!{{/fader-label}}
// 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如xxx
代码classNameBindings: ["isShowing:label-show"]的作用是根据属性isShowing的值判断是否添加CSS类label-show到标签span上。更多有关信息请看Ember.js 入门指南之十二handlebars属性绑定


{{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;
  }
}

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


Seeder, our Data Center

{{number-box title="Libraries" number=libraries.length}}
{{number-box title="Authors" number=authors.length}}
{{number-box title="Books" number=books.length}}
{{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是标记属性。

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

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

安装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上查看。可以看到数据已经存在了,并且是随机的数据。

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

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

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

家庭作业

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

Ember.js 入门指南之二十八组件定义

Ember.js 入门指南之二十九属性传递

Ember.js 入门指南之三十包裹内容

Ember.js 入门指南之三十一自定义包裹组件的HTML标签

Ember.js 入门指南之三十二处理事件

Ember.js 入门指南之三十三action触发变化



为了照顾懒人我把完整的代码放在GitHub上,如有需要请参考参考。博文经过多次修改,博文上的代码与github代码可能有出入,不过影响不大!如果你觉得博文对你有点用,请在github项目上给我点个star吧。您的肯定对我来说是最大的动力!!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79552.html

相关文章

  • 如何构建一个复杂的Ember.js项目

    摘要:本系列教材将为读者介绍怎么样使用构建一个复杂的项目。本教程分为个小部分,通过这篇文章一步步为你讲解怎么使用构建一个稍微复杂的项目。说明本教程是基于而作,请注意与你自己的版本区别,如果出现不兼容问题请自行升级项目。 文章来源:http://xcoding.tech/tags/Ember-Demo/ 声明:希望本系列教程能帮助更多学习Ember.js的初学者。 本系列教材将为读者介绍怎么样...

    djfml 评论0 收藏0
  • 弥合对象、关系之间的鸿沟(1/10)

    摘要:关键字对象关系映射现代的应用程序常常是使用两种截然不同的技术构建而成业务逻辑部分使用面向对象编程,数据存储使用关系型数据库。对象关系映射则是两者之间的桥梁,它允许应用程序以面向对象的方式访问关系数据。 O/RM技术可以简化数据访问,但也需要注意到引入这个新的抽象层来的挑战。 关键字:对象-关系映射   现代的应用程序常常是使用两种截然不同的技术构建而成:业务逻辑部分使用面向对象编程...

    tianhang 评论0 收藏0

发表评论

0条评论

raise_yang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<