Model relationships и associations

Backbone.js предоставляет минималистичный каркас для построения клиентских приложений, и одной из его ключевых возможностей является работа с моделями и коллекциями. Однако при создании сложных приложений возникает необходимость в установлении связей между моделями, что реализуется через model relationships и associations. Это позволяет организовать данные в виде структуры с вложенными объектами и связями «один ко многим» или «многие ко многим».


Основы моделей и коллекций

В Backbone.js модель (Backbone.Model) представляет собой объект с набором атрибутов и методами для работы с ними:

var Person = Backbone.Model.extend({
    defaults: {
        name: '',
        age: 0
    }
});

Коллекция (Backbone.Collection) — это упорядоченный набор моделей одного типа:

var People = Backbone.Collection.extend({
    model: Person
});

Коллекции автоматически отслеживают изменения своих моделей, а модели уведомляют коллекции о событиях изменения.


Ассоциации между моделями

Backbone.js не предоставляет встроенной системы ассоциаций, как, например, ActiveRecord в Rails, но концепцию связей можно реализовать через вложенные модели и коллекции.

Пример связи “один ко многим”:

var Task = Backbone.Model.extend({
    defaults: {
        title: '',
        completed: false
    }
});

var TaskList = Backbone.Collection.extend({
    model: Task
});

var Project = Backbone.Model.extend({
    defaults: function() {
        return {
            name: '',
            tasks: new TaskList()
        };
    },

    initialize: function() {
        this.get('tasks').parentProject = this;
    }
});

В данном примере Project содержит коллекцию tasks. Каждая задача принадлежит конкретному проекту. При инициализации проекта коллекция задач получает ссылку на родительский объект, что позволяет легко обращаться к связанной модели.


Работа с вложенными данными

Для загрузки данных с сервера, содержащих вложенные объекты, часто используют parse-функцию модели. Она позволяет корректно преобразовывать JSON-данные в вложенные модели и коллекции.

var Project = Backbone.Model.extend({
    defaults: function() {
        return {
            name: '',
            tasks: new TaskList()
        };
    },

    parse: function(response) {
        if (response.tasks) {
            this.get('tasks').reset(response.tasks);
            delete response.tasks;
        }
        return response;
    }
});

var project = new Project();
project.set(project.parse({
    name: 'Website Redesign',
    tasks: [
        {title: 'Design mockups', completed: true},
        {title: 'Implement frontend', completed: false}
    ]
}));

Метод parse позволяет отделить вложенные коллекции от основной модели, обеспечивая правильную инициализацию всех зависимых объектов.


Связи между моделями через события

Backbone.js строит реактивную архитектуру на событиях. Связи между моделями можно реализовать через подписку на события изменения атрибутов:

var Task = Backbone.Model.extend({});

var Project = Backbone.Model.extend({
    defaults: function() {
        return {
            tasks: new TaskList()
        };
    },

    initialize: function() {
        this.listenTo(this.get('tasks'), 'add remove change', this.onTasksChange);
    },

    onTasksChange: function(task) {
        console.log('Tasks updated:', task.toJSON());
    }
});

Каждое добавление, удаление или изменение задачи автоматически вызывает обработчик в родительской модели, что позволяет синхронизировать состояние модели и связанных коллекций.


Связь “многие ко многим”

Для реализации отношений «многие ко многим» создается промежуточная коллекция, которая хранит ссылки на обе стороны связи. Например, пользователи и группы:

var User = Backbone.Model.extend({});
var Group = Backbone.Model.extend({});

var Membership = Backbone.Model.extend({
    defaults: {
        user: null,
        group: null
    }
});

var Memberships = Backbone.Collection.extend({
    model: Membership
});

// Пример создания связи
var user = new User({name: 'Alice'});
var group = new Group({name: 'Admins'});

var memberships = new Memberships();
memberships.add(new Membership({user: user, group: group}));

Такой подход позволяет поддерживать гибкую структуру данных и легко реализовывать фильтрацию, выборку и обновление связей между моделями.


Рекомендации по построению ассоциаций

  1. Использовать коллекции для представления множественных связей. Это упрощает управление множественными объектами и синхронизацию с сервером.
  2. Инициализировать вложенные коллекции в defaults или initialize. Это гарантирует, что коллекции всегда доступны и корректно связаны с родительской моделью.
  3. Применять метод parse при загрузке вложенных данных. Он позволяет корректно преобразовать серверный JSON в Backbone-объекты.
  4. Использовать события для синхронизации связанных моделей. Подписка на события коллекций и моделей упрощает реактивное обновление интерфейса.

Закрепление связей через методы

Для удобства работы с ассоциациями часто создают вспомогательные методы, позволяющие получить связанные объекты или добавить новые:

var Project = Backbone.Model.extend({
    defaults: function() {
        return {tasks: new TaskList()};
    },

    addTask: function(attrs) {
        return this.get('tasks').add(new Task(attrs));
    },

    removeTask: function(task) {
        this.get('tasks').remove(task);
    },

    getCompletedTasks: function() {
        return this.get('tasks').filter(task => task.get('completed'));
    }
});

Методы инкапсулируют работу с вложенными коллекциями, обеспечивая чистый и понятный интерфейс для управления связями между моделями.