Строим блог на основе Node.js MVC фреймворка Areto

Модель статьи

Класс Article представляет модель статьи и объединяет другие сущности блога.

Просмотр статьи

В модели публичной части блога не объявляется свойство STORED_ATTRS, содержащее список сохраняемых атрибутов, так как все изменения в статью будут вноситься через модуль администрирования. В константы добавьте лишь название таблицы и строковые идентификаторы статусов:

  • draft - статья в процессе редактирования и недоступна в публичной части.
  • published - статья опубликована.
  • archived - статья снята с публикации.

models/Article.js

'use strict';
const Base = require('areto/db/ActiveRecord');
module.exports = class Article extends Base {

  static getConstants () {
    return {
      TABLE: 'article',
      STATUS_DRAFT: 'draft',
      STATUS_PUBLISHED: 'published',
      STATUS_ARCHIVED: 'archived'
    };
  }
  // place methods here
};
module.exports.init(module);
const Comment = require('./Comment');
const Photo = require('./Photo');
const User = require('./User');
const Tag = require('./Tag');

Заголовок модели соответствует заголовку статьи.

models/Article.js

...
getTitle () {
  return this.get('title');
}
...

Методы для проверки статуса статьи.

models/Article.js

...
isDraft () {
  return this.get('status') === this.STATUS_DRAFT;
}

isPublished () {
  return this.get('status') === this.STATUS_PUBLISHED;
}

isArchived () {
  return this.get('status') === this.STATUS_ARCHIVED;
}
...

Запросы выборки

Метод findPublished возвращает объект запроса всех опубликованных статей. Также для каждой статьи будут запрошены связанные данные для отношений mainPhoto, tags (см. ниже).

models/Article.js

...
static findPublished () {
  return this.find().where({status: this.STATUS_PUBLISHED}).with('mainPhoto','tags');
}
...

Метод findBySearch расширяет запрос findPublished дополнительным фильтром статей по заголовку, который должен содержать искомый текст.

models/Article.js

...
static findBySearch (text) {
  let query = this.findPublished();
  if (typeof text === 'string' && /[a-z0-9\-\s]{1,32}/i.test(text)) {
    query.andWhere(['LIKE', 'title', `%${text}%`]);
  }
  return query;
}
...

Отношения модели

Методы, начинающиеся с префикса rel, определяют отношения данной модели с другими. Отношение устанавливается через связь, которая описывается в возвращаемом запросе areto/db/ActiveQuery.

Метод relAuthor определяет автора статьи. Методы hasOne, hasMany указывают на возможное количество результатов в отношении. В данном случае у статьи может быть только один автор.

Первый параметр (User) определяет класс модели, которая связывается со статьей. Во втором параметре [User.PK, 'authorId'] указываются атрибуты, которые создают связь. От модели пользователя используется первичный ключ User.PK, а от статьи атрибут authorId, в котором хранится идентификатор пользователя.

models/Article.js

...
relAuthor () {
  return this.hasOne(User, [User.PK, 'authorId']);
}
...

Метод relPhotos возвращает запрос для выборки всех фотографий, связанных со статьей. У статьи может быть множество фотографий, поэтому используется метод hasMany.

models/Article.js

...
relPhotos () {
  return this.hasMany(Photo, ['articleId', this.PK]);
}
...

Главное фото, которое отображается в списке статей, определяется в отношении relMainPhoto. Класс Article содержит атрибут mainPhotoId, который хранит идентификатор модели класса Photo.

models/Article.js

...
relMainPhoto () {
  return this.hasOne(Photo, [Photo.PK, 'mainPhotoId']);
}
...

Метод relComments определяет комменарии, которые имеют отношение к статье. Дополнительный фильтр по статусу выдаст только одобренные комменарии для отображения в публичной части блога.

models/Article.js

...
relComments () {
  return this.hasMany(Comment, ['articleId', this.PK]).where({status: Comment.STATUS_APPROVED});
}
...

Метод relTags выбирает метки, относящиеся к статье. Каждая метка может быть связана с несколькими статьями, а каждая статья может содержать несколько меток. Такой тип отношений называется «многие ко многим» и осуществляется через промежуточную таблицу.

В методе hasMany укажите связь модели Tag с полем промежуточной таблицы, а в методе viaTable определите название таблицы связи rel_article_tag и связь таблицы с моделью Article.

models/Article.js

...
relTags () {
  return this.hasMany(Tag, [Tag.PK, 'tagId']).viaTable('rel_article_tag', ['articleId', this.PK]);
}
...