3. Node.js 应用:模型

本文由清尘发表于2019-04-06 14:53最后修改于2020-05-26属于Node.js分类

相关文章:

1. Node.js应用:MVC 框架 – Adonis
2. Node.js 应用:数据库
3. Node.js 应用:模型

=====================================

Tgit私有仓库: (lucid分支)

HTTP: https://git.code.tencent.com/testpro/shine-adonis.git
SSH:git@git.code.tencent.com:testpro/shine-adonis.git

创建模型(Model)

现在我们可以去创建一个数据模型,然后用一下它里面的方法得到数据表里的数据,再把数据放在视图里面显示出来。 一个模型会对应一个数据库里的数据表 .. 我们的应用里有个叫 posts 的数据表,里面存储的是一些文章内容 ..
这样我们就可以去创建一个叫 Post 的模型.. 数据表一般是复数的形式 .. 模型会是单数形式 ..

创建一个模型

adonis make:model Post

模型会放在 app/Models/Post.js

使用模型(Model)

修改控制器 app/Controllers/Http/PostController.js

'use strict'

const Database = use('Database')
const Post = use('App/Models/Post')
......
  async index ({ request, response, view }) {
    const posts = await Post.all()
    return posts
  }

...... 

访问 http://localhost:3333/posts 可以看到返回的资源

在视图中使用模型数据

修改控制器

......
async index ({ request, response, view }) {
    const posts = await Post.all()
    return view.render('post.index',{ posts:posts.toJSON() })
  }
...... 

新建一个视图 resources/views/post/index.edge

@layout('layouts.main')

@section('content')
  <div class="container">
    @each(post in posts)
      <h3 class="mt-5 mb-4">{{ post.title }}</h3>
      <div>{{ post.content }}</div>
    @endeach
  </div>
@endsection

访问 http://localhost:3333/posts 可以看到数据

创建资源:使用模型的 create 方法

修改控制器,使用模型的方式创建记录

......
async store ({ request, response }) {
    const newPost = request.only(['title','content'])
    // const postID = await Database.insert(newPost).into('posts')
    // console.log('postID:',postID)
    const post = await Post.create(newPost)
    return response.redirect(`/posts/${post.id}`)
  }
......

浏览器访问 http://localhost:3333/posts/create 增加一条记录,查看数据库里成功添加了数据

查询资源:使用模型的 find 方法

修改控制器

......
async show ({ params, request, response, view }) {
    // const post = await Database
    //   .from('posts')
    //   .where('id',params.id)
    //   .first()
    const post = await Post.find(params.id)
    return view.render('post.show',{post})
  }
......

浏览器访问 http://localhost:3333/posts/2 可以看到返回的数据

更新资源:使用模型的 update 方法

修改控制器

......
async edit ({ params, request, response, view }) {
    // const post = await Database
    //   .from('posts')
    //   .where('id',params.id)
    //   .first()

    const post = await Post.findOrFail(params.id)

    return view.render('post.edit',{post:post.toJSON()})
  }
......
 async update ({ params, request, response }) {
    const updatedPost = request.only(['title','content'])
    // await Database
    //   .table('posts')
    //   .where('id',params.id)
    //   .update(updatedPost)
    const post = await Post.findOrFail(params.id)
    post.merge(updatedPost)
    post.save()

  }
......

访问 http://localhost:3333/posts/2/edit 修改一条数据 。 查看数据库可以看到资源被修改

删除资源:使用模型的 delete 方法

修改控制器

......
  async destroy ({ params, request, response }) {
    // await Database
    //   .table('posts')
    //   .where('id',params.id)
    //   .delete()
    const post = await Post.find(params.id)
    post.delete()

      return 'success'
  }
......

访问 http://localhost:3333/posts/5 点击delete按钮,删除一条记录

Node.js 应用:内容关系

插入演示数据:手工

项目目录下执行

adonis repl

进入交互模式

依次执行以下代码,手动增加user数据

const User = use('App/Models/User')
const user1 = {username:'shine',email:'shine130@vip.qq.com',password:'123456'}
const user2 = {username:'shine2',email:'shine13022@vip.qq.com',password:'1234562'}
User.createMany([user1,user2])

查看数据库,可以看到新增的两条用户数据

插入演示数据:Seeds
创建一个seeds

adonis make:seed User

会提示放在 database\seeds\UserSeeder.js

修改UserSeeder.js

......
const User = use('App/Models/User')

class UserSeeder {
  async run () {
    const users = [
      {username:'张三',email:'z1@shine.com',password:'111'},
      {username:'李四',email:'z2@shine.com',password:'111'}
    ]
    User.createMany(users)
  }
}

module.exports = UserSeeder

执行

adonis seed

完成以后可以看到数据库里增加的数据

一对一关系:准备模型与数据

一个数据记录关联另外一个唯一的数据记录,这个就是数据之间的一对一的关系。比如在我们的应用里,一个用户记录,可以关联一个唯一的资料档案数据记录 .. 在这个档案数据记录上可以存储用户的一些社交网络帐号之类的数据 .. 然后使用用户 id 来指定这个档案数据所属的那个用户数据记录 ..

我们先在应用里面去添加一个新的可以存储用户档案的数据表

adonis make:migration profile
adonis make:model profile
adonis make:seed profile

再回到项目 .. 在 database .. migrations 里面,可以找到刚才我们创建的 migration .. 它会创建一个叫 profiles 的数据表 .. 它里面可以存储用户的资料档案 ..

里面再用一个 table 的 string .. 添加一个叫 github 的字段 .. 可以用它存储用户的 github 帐号 .. 你可以继续添加其它的字段来存储用户的资料档案 ..

下面我们要再添加一个关联字段 .. 用一下 table 的 interger ,字段的名字可以是 user_id .. 用一下 unsigned ,表示这个整数字段里面的值是正数的 .. 这个字段里面可以存储当前档案记录所属的用户是谁 ..

然后再用一下 table 的 foregin ,把 user_id 作为这个表的一个 foregin key ,就是外键 .. references … 相关的就是 users 表里的 id 这个字段 ..

'use strict'

/** @type {import('@adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')

class ProfileSchema extends Schema {
  up () {
    this.create('profiles', (table) => {
      table.increments()
      table.string('github')
      table.integer('user_id').unsigned()
      table.foreign('user_id').references('users.id')
      table.timestamps()
    })
  }

  down () {
    this.drop('profiles')
  }
}

module.exports = ProfileSchema

修改database/seeds/ProfileSeeder.js

...... 
const User = use('App/Models/User')

class UserSeeder {
  async run () {
    const users = [
      {username:'张三',email:'z1@shine.com',password:'111'},
      {username:'李四',email:'z2@shine.com',password:'111'}
    ]
    User.createMany(users)
  }
}
...... 

执行

adonis migration:run
adonis seed --files ProfileSeeder.js

查看数据库可以看到新增加的数据

一对一关系:描述与利用关系(使用 hasOne 方法)

我们可以说用户有一个用户档案,也可以说一个用户档案属于一个用户 .. 如果是第一种说法,我们可以在 User 模型里面使用 hasOne 去描述一下用户跟用户档案之间的关系 .. 如果是第二种说法,可以在 Profile 模型里,使用 belongsTo 去描述一下它跟用户模型之间的关系 ..

修改app/Models/User.js

......
class User extends Model {
  profile(){
    return this.hasOne('App/Models/Profile')
  }
......

创建一个控制器

adonis make:controller user --resource

修改这个控制器 app\Controllers\Http\UserController.js

const User = use('App/Models/User')
......
async show ({ params, request, response, view }) {
  const user = await User.find(params.id)
  const profile = user.profile().fetch()

  return profile
}

......

修改路由 ,增加一个资源类型的路由

......
Route.resource('users','UserController')
......

访问http://localhost:3333/users/1 可以看到返回的用户档案

修改控制器指定需要返回的字段

......
async show ({ params, request, response, view }) {
  const user = await User.find(params.id)
  const profile = user.profile().select('github').fetch()

  return profile
}
......

访问http://localhost:3333/users/1 可以看到只返回了需要的字段

现在我需要得到的响应里面包含用户相关的信息,修改控制器

async show ({ params, request, response, view }) {
  const user = await User.find(params.id)
  const { username,email} = user.toJSON()
  const profile = await user.profile().select('github').fetch()

  return {
    username,
    email,
    profile:profile.toJSON()
  }
}

再次访问 http://localhost:3333/users/1 可以看到返回的信息

一对一关系:描述与利用关系(使用 belongsTo 方法)

我们之前在 User 模型里用 hasOne 描述了一下用户拥有一个档案这种关系,在另一边,也就是用户档案这边儿,可以使用 belongsTo ,描述一下,用户档案属于某个用户这种关系 ..

修改app/Models/Profile.js

......
class Profile extends Model {
  user(){
    return this.belongsTo('App/Models/User')
  }
}
......

修改routes.js

......
const Route = use('Route')
const Profile = use('App/Models/Profile')

Route.on('/').render('welcome')
Route.resource('posts','PostController')
Route.resource('users','UserController')

Route.get('profiles/:id',async ({params}) => {
  const profile = await Profile.find(params.id)
  const user = await profile.user().select('username').fetch()

  return {
    profile,
    user
  }

})

访问一下 http://localhost:3333/profiles/1 可以看到返回的数据

一对多关系:准备模型与数据

一条数据记录跟多个数据记录相关联,这个就是数据之间的一对多的关系。比如在我们的应用里,一个用户可以跟多个文章资源相关联 .. 因为一个用户可以是多个文章内容的作者 .. 这种关系可以使用模型的 hasMany 来描述一下 ..

新建一个migration为post表增加user_id字段

adonis make:migration posts_add_user_id_column

选择Select table
修改刚刚生成的database\migrations\1554780769920_posts_add_user_id_column_schema.js

......
class PostsAddUserIdColumnSchema extends Schema {
  up () {
    this.table('posts', (table) => {
      table.integer('user_id').unsigned()
      table.foreign('user_id').references('users.id')
    })
  }

  down () {
    this.table('posts', (table) => {
      table.dropForeign('user_id')
      table.dropColumn('user_id')
    })
  }
}
......

执行

adonis migration:run

再添加一个seed添加一点演示数据

adonis make:seed Post

database\seeds\PostSeeder.js

......
const Post = use('App/Models/Post')

class PostSeeder {
  async run () {
    const posts = [
      { title: '停用社交网络', content: '这个月停用社交网络,专心工作,学习,顺道思考一下人生。', user_id: 1 },
      { title: 'Node.js 课程', content: '继续制作一批 Node.js 应用开发课程。', user_id: 1 },
      { title: '改造宁皓网', content: '为了能更好的实行推广,需要改造一下宁皓网,或许可以试试重做,大工程 ~', user_id: 1 },
      { title: '幼儿园作业', content: '完成了幼儿园老师布置给家长的作业,带小羽到大自然体验春天。', user_id: 2 },
      { title: '计划装修', content: '今年有个大工程,需要装修个小房子,哎呀,全得靠我自己啊。', user_id: 2 },
      { title: '北海道面包', content: '再做两个北海道面包,王皓说他喜欢吃。', user_id: 2 }
    ]

    await Post.createMany(posts)

  }
}
......

执行

adonis seed --files PostSeeder.js

浏览一下数据库会有刚刚插入的数据

一对多关系:描述与利用关系(使用 hasMany 方法)

在app/Models/User.js 里描述一下和文章模型的一对多的关系

......
posts(){
  return this.hasMany('App/Models/Post')
}
......

修改user的控制器 app/Controllers/Http/UserController.js

......
async show ({ params, request, response, view }) {
  const user = await User.find(params.id)
  const { username,email} = user.toJSON()
  const profile = await user.profile().select('github').fetch()
  const posts = await user.posts().select('title','content').fetch()

  return {
    username,
    email,
    profile,
    posts
  }
}
......

用客户端访问 http://localhost:3333/users/1 可以看返回的用户文章列表

多对多关系:准备模型与数据
在应用里,我们可以给文章打上一些标签 .. 一个文章上可以有多个标签 .. 每个标签可以属于多个文章 .. 这个就是一种多对多的数据关系 .. 利用这种关系,我们可以在文章上显示给它打的标签,可以在某个标签页面上显示它所属的一些文章内容 .

创建一个tag migration

adonis make:migration tag

选择create table

修改刚刚创建的 database\migrations\1554789733774_tag_schema.js 增加一个title字段

......
this.create('tags', (table) => {
  table.increments()
  table.string('title')
  table.timestamps()
......

执行

adonis migration:run

创建一个seed 和 model

adonis make:seed tag
adonis make:model tag

修改刚才创建的 database\seeds\TagSeeder.js

......
class TagSeeder {
  async run () {
    const tags = [
      {title:'工作'},
      {title:'日常'}
    ]
    await Tag.createMany(tags)
  }
}
......

运行一下这个seed

adonis seed --files TagSeeder.js

再查看数据库已经有tag表的数据了

多对多关系:准备 pivot 数据表

在文章与标签之间创建多对多的关系,需要用到一个中间表 .. 这种表有个专门的名字叫 pivot table .. 比如对于我们要创建的标签跟文章之间的关系,添加一个 pivot table ,名字可以是 post_tag ..这个表上面,每条数据记录要包含文章的 id 号,还有所属的标签的 id 号 ..

adonis make:migration post_tag
adonis make:seed PostTagPivot

修改 database\migrations\1554790817996_post_tag_schema.js

......
up () {
  this.create('post_tag', (table) => {
    table.increments()
    table.integer('post_id').unsigned()
    table.foreign('post_id').references('posts.id')
    table.integer('tag_id').unsigned()
    table.foreign('tag_id').references('tags.id')
  })
}

down () {
  this.drop('post_tag')
}
......

修改 database\seeds\PostTagPivotSeeder.js

......
const Database = use('Database')

class PostTagPivotSeeder {
  async run () {
    await Database
      .table('post_tag')
      .insert([
        { post_id: 1, tag_id: 2 },
        { post_id: 1, tag_id: 1 },
        { post_id: 2, tag_id: 1 },
        { post_id: 3, tag_id: 1 },
        { post_id: 4, tag_id: 2 },
        { post_id: 5, tag_id: 1 },
        { post_id: 6, tag_id: 2 },
        { post_id: 6, tag_id: 1 }
      ])
  }
}

......

执行

adonis migration:run
adonis seed --files PostTagPivotSeeder.js

多对多关系:描述与利用关系(使用 belongsToMany 方法)
现在我们可以在文章模型里面去描述一下它跟标签之间的关系
修改app/Models/Post.js

......
class Post extends Model {
  tags () {
    return this.belongsToMany('App/Models/Tag')
  }
}
......

修改 PostController.js

......
async show ({ params, request, response, view }) {
  // const post = await Database
  //   .from('posts')
  //   .where('id',params.id)
  //   .first()
  const post = await Post.findOrFail(params.id)
  const tags = await post.tags().select('id','title').fetch()
  return view.render('post.show',{post,tags:tags.toJSON()})
}

......

修改show.edge 视图

{{post.content}}
@if(tags)
<div class="my-5">
  <span class="text-muted font-weight-light">标签:</span>
  @each(tag in tags)
    <a href="/tags/{{tag.id}}" class="badge badge-pill badge-secondary px-2 py-1">{{tag.title}}</a>
  @endeach
</div>
@endif

用浏览器访问 http://localhost:3333/posts/1 可以看到返回的文章带上了标签

多对多关系:创建标签页面显示相关文章列表

点击标签链接,可以显示一个标签页面,上面会显示当前打开的标签相关的一些文章内容

创建一个资源控制器

adonis make:controller tag --resource

修改路由 routes.js

......
Route.resource('tags','TagController')
......

修改app/Models/Tag.js

......
class Tag extends Model {
  posts () {
    return this.belongsToMany('App/Models/Post')
  }
}

......

修改 app\Controllers\Http\TagController.js

......
async show ({ params, request, response, view }) {
  const tag = await Tag.find(params.id)
  const posts = await tag.posts().select('id','title','content').fetch()
  return view.render('tag.show',{tag,posts:posts.toJSON()})
}

......

新建一个tag视图,放在resources/views/tag/show.edge

@layout('layouts.main')

@section('content')
  <div class="container">
    <h1 class="mt-5 mb-4 font-weight-light">{{tag.title}}</h1>
    @if(posts)
      <div class="my-5">
        @each(post in posts)
          <h3 class="mt-5 mb-4 font-weight-light">{{post.title}}</h3>
          <div>
            {{post.content}}
          </div>
        @endeach
      </div>
    @endif
  </div>
@endsection

用浏览器访问 http://localhost:3333/posts/1 点击标签 页面上会显示标签的标题 .. 还有标签相关的一个文章列表 ….