Active Record для Node.js з API від Yii 2

JiiВступ
Привіт всім хабровчанам, любителям Yii та Node.js.
Це друга стаття про фреймворк Jii GitHub, попередній статті ми розглядали Об'єкти доступу до даних і конструктор запитів (Query Builder).
Як і обіцяв, у цій статті я розповім про використання Active Record.

Active Record
Active Record забезпечує об'єктно-орієнтований інтерфейс для доступу і маніпуляції даними, що зберігаються в базах даних. Клас Active Record пов'язаний з таблицею бази даних, екземпляр класу відповідає рядку цієї таблиці, а атрибути являють собою значення певного стовпця в цьому рядку. Замість того щоб писати SQL запити в явному вигляді, Ви маєте доступ до атрибутів Active Record і методів, що маніпулюють даними.

Припустимо, що у нас є Active Record клас `app.models.Customer`, який пов'язаний з таблиці `customer` і `name` це ім'я колонки в таблиці `customer`. Ви можете написати наступний код для додавання нового рядка в таблицю `customer`:

var customer = new app.models.Customer();
customer.name = 'Vladimir';
customer.save();

Це еквівалентно наступному кодом, в якому Ви можете допустити більше помилок при написанні і де можуть бути несумісності при різних видах даних:

db.createCommand('INSERT INTO `customer` (`name`) VALUES (:name)', {
':name': 'Vladimir',
}).execute();

Оголошення класів Active Record
Для початку, оголосіть клас Active Record, успадкувавши Jii.sql.ActiveRecord. Кожен Active Record клас пов'язаний з таблицею бази даних, тому в цьому класі необхідно перевизначити статичний метод Jii.sql.ActiveRecord.tableName() для вказівки з якою таблицею пов'язаний клас.

У наступному прикладі ми оголошуємо клас з ім'ям `app.models.Customer` для таблиці `customer`.

var Jii = require('jii');

/**
* @class app.models.Customer
* @extends Jii.sql.ActiveRecord
*/
Jii.defineClass('app.models.Customer', /** @lends app.models.Customer.prototype */{

__extends: Jii.sql.ActiveRecord,

__static: /** @lends app.models.Customer */{

tableName: function() {
return 'customer';
}

}

});

Клас Active Record є моделлю, тому зазвичай ми кладемо Active Record класи в просторі імен `models`.

Клас Jii.sql.ActiveRecord успадковує Jii.base.Model, це означає, що він успадковує всі можливості моделей, такі як атрибут, правила валідації, серелиализация даних і т. д.

Створення з'єднання з БД
За замовчуванням, Active Record використовує компонент програми `db`, який містить примірник Jii.sql.BaseConnection для читання і зміни даних в БД. Як описано в попередній статті, в розділі «Об'єкти доступу до бази даних», ви можете налаштувати компонент програми `db` наступним чином:

return {
components: {
db: {
className: 'Jii.sql.mysql.Connection',
host: '127.0.0.1',
database: 'testdb',
username: 'demo',
password: 'demo',
charset: 'utf8',
}
}
};

Якщо ви хочете використовувати інше з'єднання з базою даних, ви повинні перевизначити метод Jii.sql.ActiveRecord.getDb():

/**
* @class app.models.Customer
* @extends Jii.sql.ActiveRecord
*/
Jii.defineClass('app.models.Customer', /** @lends app.models.Customer.prototype */{

__extends: Jii.sql.ActiveRecord,

__static: /** @lends app.models.Customer */{

STATUS_INACTIVE: 0,
STATUS_ACTIVE: 1,

// ...

getDb: function() {
// use the "db2" application component
return Jii.app.db2;
}

}

});

Вибірка даних

Після оголошення класу Active Record, ви можете використовувати його для запиту даних з відповідної таблиці БД.
Для цього необхідно виконати три дії:

1. Створити новий об'єкт запиту шляхом виклику методу Jii.sql.ActiveRecord.find();
2. Згенерувати об'єкт запиту, викликаючи методи створення запитів;
3. Викликати один з методів запиту для отримання даних у вигляді записів Active Record.

Ці дії дуже схожі з діями при роботі з конструктором запитів. Різниця тільки в тому, що для створення об'єкта запиту необхідно викликати метод Jii.sql.ActiveRecord.find(), а не створювати екземпляр через `new`.

Розглянемо кілька прикладів, що показують, як використовувати Active Query для отримання даних:

// Повертає клієнта з ID 123
// SELECT * FROM `customer` WHERE `id` = 123
app.models.Customer.find()
.where({id: 123})
.one()
.then(function(customer) {
// ...
});

// Повертає всіх активних клієнтів, відсортованих по ID
// SELECT * FROM `customer` WHERE `status` = 1 ORDER BY `id`
app.models.Customer.find()
.where({status: app.models.Customer.STATUS_ACTIVE})
.orderBy('id')
.all()
.then(function(customers) {
// ...
});

// Повертає кількість активних клієнтів
// SELECT COUNT(*) FROM `customer` WHERE `status` = 1
app.models.Customer.find()
.where({status': app.models.Customer.STATUS_ACTIVE})
.count()
.then(function(count) {
// ...
});

// Повертає всіх клієнтів у вигляді об'єкта, де ключами є ID клієнтів
// SELECT * FROM `customer`
app.models.Customer.find()
.indexBy('id')
.all()
.then(function(customers) {
// ...
});

Для спрощення отримання моделей ID створено методи:
  • Jii.sql.ActiveRecord.findOne(): Повертає екземпляр Active Record, відповідний першому рядку результату апроса.
  • Jii.sql.ActiveRecord.findAll(): Повертає масив або об'єкт кількох Active Record, що відповідають рядкам результату запиту.
Обидва методу приймають перший аргумент наступного формату:
  • Скалярний значення: значення розглядається як значення первинного ключа, за яким йде пошук. Первинний ключ визначається автоматично з схеми БД.
  • Масив скалярних значень: масив розглядається як значення первинного ключа, за яким йде пошук. — Об'єкт, ключами якого є імена стовпців, а значення відповідають значенням стовпців, по яких йде пошук.
Наступні приклади показують, як ці методи можуть бути використані:

// Повертає клієнта з ID 123
// SELECT * FROM `customer` WHERE `id` = 123
app.models.Customer
.findOne(123)
.then(function(customer) {
// ...
});

// Повертає клиентовс ID 100, 101, 123 або 124
// SELECT * FROM `customer` WHERE `id` IN (100, 101, 123, 124)
app.models.Customer
.findAll([100, 101, 123, 124])
.then(function(customers) {
// ...
});

// Повертає активного клієнта з ID 123
// SELECT * FROM `customer` WHERE `id` = 123 AND `status` = 1
app.models.Customer
.findOne({
id: 123,
status: app.models.Customer.STATUS_ACTIVE
})
.then(function(customer) {
// ...
});

// Повертає всіх неактивних клієнтів
// SELECT * FROM `customer` WHERE `status` = 0
app.models.Customer
.findAll({
status: app.models.Customer.STATUS_INACTIVE
})
.then(function(customers) {
// ...
});

Примітка: Jii.sql.ActiveRecord.findOne() ні Jii.sql.ActiveQuery.one() не додадуть `LIMIT 1` SQL-вираз. Якщо Ваш запит дійсно може повернути безліч даних, то необхідно викликати `limit(1)` для встановлення межі, наприклад `app.models.Customer.find().limit(1).one()`.
Ви можете так само використовувати звичайні запити SQL для отримання даних та заповненнях їх в Active Record. Для цього необхідно використовувати метод Jii.sql.ActiveRecord.findBySql():

// Повертає всіх неактивних клієнтів
var sql = 'SELECT * FROM customer WHERE status=:status';
app.models.Customer
.findBySql(sql, {':status': app.models.Customer.STATUS_INACTIVE})
.all()
.then(function(customers) {
// ...
});

Не викликайте методи створення запиту після виклику Jii.sql.ActiveRecord.findBySql(), вони будуть ігноруватися.

Доступ до даних

Як згадувалося вище, примірники Active Record заповнюються даними з результатів SQL запиту, і кожна рядок результату запиту відповідає одному примірнику Active Record. Ви можете отримати доступ до значень стовпців через атрибути Active Record, наприклад,

// Імена стовпців "id" і "email" з таблиці "customer"
app.models.Customer
.findOne(123)
.then(function(customer) {
var id = customer.get('id');
var email = customer.get('email');
});

Отримати дані в об'єктах
Одержання дані як Active Record зручно, але іноді це може бути неоптимально з-за великого споживання пам'яті, яке витрачається на створення примірників Active Record. У цьому випадку ви можете отримати їх як звичайні об'єкти, для цього потрібно викликати метод Jii.sql.ActiveQuery.asArray().
По факту, JavaScript ви отримаєте масив, наповнений об'єктами. Тому правильніше було б назвати метод asObject(), і такий метод (синонім). Але для збереження API Yii 2 залишений метод asArray().
// Повертає всіх клієнтів, кожен з яких
// представлений як об'єкт
app.models.Customer.find()
.asArray() // alias is asObject()
.all()
.then(function(customers) {
// ...
});

Збереження даних
Изпользуя Active Record, Ви можете зберігати дані в БД виконавши наступні кроки:
  1. Отримаєте або створіть екземпляр Active Record;
  2. Виберіть нові значення атрибутів
  3. Викличте метод Jii.sql.ActiveRecord.save() для збереження даних.
Наприклад,

// Додавання нового рядка в таблицю
var customer = new app.models.Customer();
customer.set('name', 'James');
customer.set('email', 'james@example.com');
customer.save().then(function(success) {

return app.models.Customer.findOne(123);
}).then(function(customer) {

// Оновлення даних
customer.set('email', 'james@newexample.com');
return customer.save();
}).then(function(success) {
// ...
});

Метод Jii.sql.ActiveRecord.save() може додати або оновити дані рядки, у залежності від стану Active Record. Якщо экземплер був створений за допомогою оператора `new`, то метод додасть новий рядок. Якщо примірник отримано через метод find() і йому подібні або вже був викликаний метод save() раніше, то метод save()
оновить дані.

Валідація даних
Клас Jii.sql.ActiveRecord успадковується від Jii.base.Model, тому в ньому доступна валідація даних. Ви можете задати правила вализации через перевизначення методу Jii.sql.ActiveRecord.rules() і перевірити на правильність значень через метод Jii.sql.ActiveRecord.validate().

Коли ви викликаєте метод Jii.sql.ActiveRecord.save(), за замовчуванням, автоматично буде викликаний метод Jii.sql.ActiveRecord.validate(). Тільки перевірені дані повинні зберігатися в БД; Якщо дані не вірні, то метод поверне `false` і Ви можете отримати через помилку метод Jii.sql.ActiveRecord.getErrors() або йому подібні.

Зміна безлічі атрибутів
Як і звичайні моделі, примірник Active Record так само підтримує зміну атрибутів через передачу об'єкта. Використовуючи цей спосіб, ви можете присвоїти значення декількох атрибутів Active Record через виклик одного методу. Пам'ятайте, що тільки безпечні атрибути можуть бути масово присвоєні.

var values = {
name: 'James',
email: 'james@example.com'
};

var customer = new app.models.Customer();

customer.setAttributes(values);
customer.save();

Змінені атрибути
Коли ви викликаєте метод Jii.sql.ActiveRecord.save(), відбувається збереження тільки змінених атрибутів Active Record. Атрибут вважається зміненим, якщо було змінено його значення. Зверніть увагу, що перевірка даних буде виконуватися незалежно від існування змінених атрибутів.

Active Record автоматично зберігає список змінених атрибутів. Вона зберігає старі версії атрибутів і порівнює їх з останньою версією. Ви можете отримати змінені атрибути через метод Jii.sql.ActiveRecord.getDirtyAttributes().

Для отримання старих значень атрибутів, викликайте метод Jii.sql.ActiveRecord.getOldAttributes() або Jii.sql.ActiveRecord.getOldAttribute().

Значення за замовчуванням
Деякі з ваших стовпців в таблиці можуть мати значення за замовчуванням, визначені в базі даних. Ви можете попередньо заповнити Active Record цими значеннями, викликавши метод Jii.sql.ActiveRecord.loadDefaultValues(). Цей метод синхронний, т. к. схема БД заздалегідь підвантажується при відкритті з'єднання.

var customer = new app.models.Customer();
customer.loadDefaultValues();
// customer.get('xyz' Значення атрибуту `xyz` буде відповідати значенням за замовчуванням для стовпця `xyz`.

Оновлення декількох рядків
Описані вище методи працюють з экземплярамм Active Record. Щоб оновити кілька рядків, ви можете викликати статичний метод Jii.sql.ActiveRecord.updateAll():

// UPDATE `customer` SET `status` = 1 WHERE `email` LIKE `%@example.com%`
app.models.Customer.updateAll({status: app.models.Customer.STATUS_ACTIVE}, {'like', 'email', '@example.com'});

Видалення даних
Для видалення рядка з таблиці, необхідно у эксемпляра Active Record, відповідної цьому рядку, викликати метод Jii.sql.ActiveRecord.delete().

app.models.Customer
.findOne(123)
.then(function(customer) {
customer.delete();
});

Ви можете викликати статичний метод Jii.sql.ActiveRecord.deleteAll() для видалення безлічі рядків по умові.
Наприклад,

app.models.Customer.deleteAll({status: app.models.Customer.STATUS_INACTIVE});

Робота з пов'язаними даними

Крім роботи з окремими таблицями бази даних, Active Record здатний зв'язати дані через первинні дані. Наприклад, дані про клієнтів можуть бути пов'язані з замовленнями. При оголошенні відповідної зв'язку в Active Record, Ви можете отримати інформацію про замовлення клієнта, використовуючи вираз `customer.load('orders')`, при цьому на виході ви отримаєте масив примірників `app.models.Order`.

Оголошення залежностей
Для роботи з реляційними даними за допомогою Active Record, спочатку потрібно оголосити відношення в класі Active Record. Наприклад,

/**
* @class app.models.Customer
* @extends Jii.sql.ActiveRecord
*/
Jii.defineClass('app.models.Customer', /** @lends app.models.Customer.prototype */{

// ...

getOrders: function() {
return this.hasMany(app.models.Order.className(), {customer_id: 'id'});
}

});

/**
* @class app.models.Order
* @extends Jii.sql.ActiveRecord
*/
Jii.defineClass('app.models.Order', /** @lends app.models.Order.prototype */{

// ...

getCustomer: function() {
return this.hasOne(app.models.Customer.className(), {id: 'customer_id'});
}

});

У наведеному вище коді, ми оголосили співвідношення `orders` класу `app.models.Customer`, і ставлення `customer` класу `app.models.Order`.

Кожен метод ставлення повинен бути названий `getXyz` (get + ім'я відносини з першою буквою в нижньому регістрі). Зверніть увагу, що імена відносин є *чутливими до регістру*.

У відношенні, ви повинні вказати наступну інформацію:

  • Кратність зв'язку: вказується при виклику методів Jii.sql.ActiveRecord.hasMany() або Jii.sql.ActiveRecord.hasOne(). У наведеному вище прикладі у клієнта багато замовлень, а у замовлення тільки один клієнт.
  • Назва пов'язаного класу Active Record: вказується в якості першого параметра у вище названих методів. Рекомендується отримувати ім'я класу через `Xyz.className()`, щоб, по-перше, перевірити існування класу ще на етапі конструювання відносини, а по-друге, щоб IDE підказувала Вам ім'я класу при написанні.
  • Зв'язок між двома схемами таблиць: визначає стовпець (и), через який пов'язані два типи даних. Значеннями об'єкта є стовпці первинних даних, а ключами — стовпці пов'язаних даних.
Доступ до пов'язаних даними
Після оголошення відносини, ви можете отримати доступ до пов'язаних даними через ім'я ставлення. Якщо Ви впевнені, що пов'язані дані вже завантажено в Active Record, то можна отримати пов'язані Active Record аналогічно доступу до властивостям об'єкта через метод get(). Інакше, краще використовувати метод Jii.sql.ActiveRecord.load() для завантаження пов'язаних даних, який буде завжди повертати об'єкт `Promise`, але не буде слати зайвий запит до БД, якщо зв'язок вже була підвантажена раніше.

// SELECT * FROM `customer` WHERE `id` = 123
app.models.Customer
.findOne(123)
.then(function(customer) {
// SELECT * FROM `order` WHERE `customer_id` = 123
return customer.load('orders');
})
.then(function(orders) {
// orders - масив екземплярів класу `app.models.Order`
});

Якщо відношення оголошено методом Jii.sql.ActiveRecord.hasMany(), то ці відносини будуть представлені масивом примірників Active Record (або порожнім масивом). Якщо методом Jii.sql.ActiveRecord.hasOne(), то ці відносини будуть представлені примірником Active Record або `null`, якщо дані відсутні.

При доступі до відношенню в перший раз, буде виконаний SQL-запит до БД, як показано в прикладі вище. При повторному зверненні, запит виконуватися не буде.

Відносини через додаткову таблицю (Junction Table)
При моделюванні баз даних, коли зв'язок між двома таблицями Many-Many, то зазвичай додається додаткова таблиця — Junction Table. Наприклад, таблиця `order` таблиця `item` можуть бути пов'язані з допомогою таблиці `order_item`.

При оголошенні таких відносин, Вам потрібно викликати методи Jii.sql.ActiveQuery.via() або Jii.sql.ActiveQuery.viaTable() з зазначенням додатковий таблиці. Різниця між цими методами у тому, що перший вказує таблицю переходу з точки зору поточного імені відносини, в той час як останній безпосередньо додаткову таблицю. Наприклад,

/**
* @class app.models.Order
* @extends Jii.sql.ActiveRecord
*/
Jii.defineClass('app.models.Order', /** @lends app.models.Order.prototype */{

// ...

getItems: function() {
return this.hasMany(app.models.Item.className(), {id: 'item_id'})
.viaTable('order_item', {order_id: 'id'});
}

});

або альтернативно,

/**
* @class app.models.Order
* @extends Jii.sql.ActiveRecord
*/
Jii.defineClass('app.models.Order', /** @lends app.models.Order.prototype */{

// ...

getOrderItems: function() {
return this.hasMany(app.models.OrderItem.className(), {order_id: 'id'});
},

getItems: function() {
return this.hasMany(app.models.Item.className(), {id: 'item_id'})
.via('orderItems');
}

});

Використання відносин, оголошених з додатковою таблицею, аналогічно звичайним відносин. Наприклад,

// SELECT * FROM `order` WHERE `id` = 100
app.models.Order
.findOne(100)
.then(function(order) {

// SELECT * FROM `order_item` WHERE `order_id` = 100
// SELECT * FROM `item` WHERE `item_id` IN (...)
return order.load('items');
})
.then(function(items) {
// items - масив примірників `app.models.Item`
});

Лінива Завантаження і жадібне завантаження
У розділі доступу до пов'язаних даними, ми розповідали, що ви можете отримати доступ до відношенню Active Record через методи get() або load(). SQL запит буде відправляти в БД тільки при першому зверненні до пов'язаних даними. Такі способи завантаження даних назваются ледачими (lazy loading).
Наприклад,

// SELECT * FROM `customer` WHERE `id` = 123
app.models.Customer
.findOne(123)
.then(function(customer) {
// SELECT * FROM `order` WHERE `customer_id` = 123
customer.load('orders').then(function(orders) {

// SQL запит не відправляється
return customer.load('orders');
}).then(function(orders2) {

// Після завантаження зв'язків, вони доступні також через метод <i>get()</i>.
var orders3 = customer.get('orders');
});
});


Лінивий навантаження дуже зручна у використанні. Тим не менш, це може викликати проблеми з продуктивністю, коли вам потрібно отримати доступ до пов'язаних заним для безлічі екземплярів Active Record. Розглянемо наступний приклад коду, скільки SQL запитів буде виконано?

// SELECT * FROM `customer` LIMIT 100
app.models.Customer.find()
.limit(100)
.all()
.then(function(customers) {
return Promise.all(customers.map(function(customer) {

// SELECT * FROM `order` WHERE `customer_id` = ...
return customer.load('orders');
}));
}).then(function(result) {
var firstOrder = result[0][0];
// ...
});

В даному прикладі виконається 101 SQL запит! Тому що для кожного клієнта будуть отримані заявки через окремий запит. Щоб вирішити цю проблему продуктивності, можна використовувати підхід *жадібного завантаження*, як показано нижче,

// SELECT * FROM `customer` LIMIT 100;
// SELECT * FROM `orders` WHERE `customer_id` IN (...)
app.models.Customer.find()
.with('orders')
.limit(100)
.all()
.then(function(customers) {
customers.forEach(function(customer) {

// без запиту SQL
var orders = customer.get('orders');
});
});

Ви можете завантажити разом з основною записом одне або кілька відносин. Ви навіть можете завантажити відразу і вкладені відносини. Наприклад, якщо `app.models.Customer` пов'язаний з` app.models.Order` через відношення `orders`, а `app.models.Order` пов'язаний з `Item` через `items`. При запиті `app.models.Customer`, ви можете відразу завантажити ставлення `items` вказавши в методі with() `orders.items`.

Наступний код показує різні використання Jii.sql.ActiveQuery.with(). Ми припускаємо, що клас `app.models.Customer` має два відносини: `orders` і `country`, в той час як клас `app.models.Order` має одне співвідношення — `items`.

// Примусова завантаження відносин "orders" і "country"
app.models.Customer.find()
.with('orders', 'country')
.all()
.then(function(customers) {
// ...
});

// це еквівалентно запису через масив
app.models.Customer.find()
.with(['orders', 'country'])
.all()
.then(function(customers) {
// без запиту SQL
var orders = customers[0].get('orders');
var country = customers[0].get('country');
});

// Примусова завантаження відносини "orders" і вкладеного відносини "orders.items"
app.models.Customer.find()
.with('orders.items')
.all()
.then(function(customers) {

// Отримання пунктів з першого замовлення для першого клієнта
// без запиту SQL
var items = customers[0].get('orders')[0].get('items');
});

Ви можете завантажити примусово глибоко вкладені відносини, такі як `a.b.c.d`. Всі батьківські відносини будуть примусово завантажені.

При жадібною завантаженні відносин, ви можете налаштувати відповідний запит, передавши анонімну функцію. Наприклад,

// Пошук клієнтів разом з їхніми країнами а активними замовленнями
// SELECT * FROM `customer`
// SELECT * FROM `country` WHERE `id` IN (...)
// SELECT * FROM `order` WHERE `customer_id` IN (...) AND `status` = 1
app.models.Customer.find()
.with({
country: 'country',
orders: function (query) {
query.andWhere({'status': app.models.Order.STATUS_ACTIVE});
}
})
.all()
.then(function(customers) {
// ...
});

При налаштуванні реляційного запиту для зв'язку, ви повинні вказати ім'я відносини у якості ключа об'єкта і використовувати анонімну функцію як значення відповідного об'єкта. Першим агрументом нонимной функції буде параметр `query`, який представляє собою об'єкт Jii.sql.ActiveQuery. У прикладі вище, ми змінюємо запиту шляхом додавання додаткової умови про статус замовлення.

Зворотні відносини
Відносини між класами Active Record найчастіше назад пов'язані один з одним. Наприклад, клас `app.models.Customer` пов'язаний з `app.models.Order` через відношення `orders`, а клас `app.models.Order` назад пов'язаний з класом `app.models.Customer` через відношення `customer`.

/**
* @class app.models.Customer
* @extends Jii.sql.ActiveRecord
*/
Jii.defineClass('app.models.Customer', /** @lends app.models.Customer.prototype */{

// ...

getOrders: function() {
return this.hasMany(app.models.Order.className(), {customer_id: 'id'});
}

});

/**
* @class app.models.Order
* @extends Jii.sql.ActiveRecord
*/
Jii.defineClass('app.models.Order', /** @lends app.models.Order.prototype */{

// ...

getCustomer: function() {
return this.hasOne(app.models.Customer.className(), {id: 'customer_id'});
}

});

Тепер розглянемо наступний фрагмент коду:

// SELECT * FROM `customer` WHERE `id` = 123
app.models.Customer
.findOne(123)
.then(function(customer) {
// SELECT * FROM `order` WHERE `customer_id` = 123
return customer.load('orders');
}).then(function(orders) {
var order = orders[0];

// SELECT * FROM `customer` WHERE `id` = 123
return order.load('customer');
}).then(function(customer2) {

// Відображає "not the same"
console.log(customer2 === customer ? 'same' : 'not the same');
});

Ми припускаємо, що об'єкти `customer` і `customer2` є однаковими, але насправді це не так. Вони містять однакові дані є різними екземплярами. При доступі до `order.customer` виконується додатковий SQL запит для отримання нового об'єкта `customer2`.

Щоб уникнути надлишкового виконання останнього SQL запиту в наведеному вище прикладі, ми повинні вказати, що `customer` є зворотною залежністю* від `orders` з допомогою методу Jii.sql.ActiveQuery.inverseOf().

/**
* @class app.models.Customer
* @extends Jii.sql.ActiveRecord
*/
Jii.defineClass('app.models.Customer', /** @lends app.models.Customer.prototype */{

// ...

getOrders: function() {
return this.hasMany(app.models.Order.className(), {customer_id: 'id'}).inverseOf('customer');
}

});

Після цих змін ми отримаємо:

// SELECT * FROM `customer` WHERE `id` = 123
app.models.Customer
.findOne(123)
.then(function(customer) {
// SELECT * FROM `order` WHERE `customer_id` = 123
return customer.load('orders');
}).then(function(orders) {
var order = orders[0];

// SELECT * FROM `customer` WHERE `id` = 123
return order.load('customer');
}).then(function(customer2) {

// Відображає "same"
console.log(customer2 === customer ? 'same' : 'not the same');
});

Зауваження: Зворотні відносини не працюють для відносин Many-Many, оголошені з додатковою таблцей (Junction Table).
Збереження залежностей

При роботі з реляційними даними, часто необхідно додати відносини між різними даними або видалити існуючі відносини. Для цього потрібно встановити правильні значення для стовпців, які визначають відносини. З застосуванням Active Record Ви можете зробити це приблизно так:

app.models.Customer
.findOne(123)
.then(function(customer) {
var order = new app.models.Order();
order.subtotal = 100;

// ...

// Встановлюємо значення, що визначає ставлення "customer" для `app.models.Order` і зберігаємо.
order.customer_id = customer.id;
order.save();
});

Active Record надає метод Jii.sql.ActiveRecord.link(), який дозволяє зробити це більш витончено:

app.models.Customer
.findOne(123)
.then(function(customer) {
var order = new app.models.Order();
order.subtotal = 100;

// ...

order.link('customer', customer);
});

Метод Jii.sql.ActiveRecord.link() очікує ім'я відносини і экзепмляр Active Record, з яким з'єднана запис. Метод з'єднає два примірники Active Record і збереже їх в БД. У наведеному вище прикладі, він встановить атрибут `customer_id` в `app.models.Order`.
Примітка: Ви не можете зв'язати два тільки що створених примірників Active Record.
Вигода від використання методу Jii.sql.ActiveRecord.link() ще більш очевидною, коли ставлення визначається за допомогою додаткової таблицею. Наприклад, ви можете використовувати наступний код, щоб зв'язати примірник `app.models.Order` з `app.models.Item`:

order.link('items', item);

Цей код автоматично додасть рядок в таблицю `order_item` для створення зв'язку.

Для видалення зв'язку між двома примірниками Active Record, використовуйте метод Jii.sql.ActiveRecord.unlink()|unlink().
Наприклад,

app.models.Customer.find()
.with('orders')
.all()
.then(function(customer) {
customer.unlink('orders', customer.get('orders')[0]);
});

За замовчуванням, метод Jii.sql.ActiveRecord.unlink() метод встановлює значення ключа, що визначає ставлення, `null`. Однак, ви можете передати параметр `isDelete` як `true`, щоб видалити рядки з таблиці.

В ув'язненні


На даний момент в Active Record від Jii не реалізовані транзакції, річ потрібна і тому в майбутньому вона з'явиться.
Як я вже говорив в попередній статті, Jii — опенсорсний проект, тому я буду дуже радий, якщо хтось приєднається до розробки Jii. Пишіть на affka@affka.ru.

Сайт фреймворку — jiiframework.uk
GitHub — github.com/jiisoft
Як ви оцінюєте ідею Jii?

/>
/>


<input type=«radio» id=«vv67565»
class=«radio js-field-data»
name=«variant[]»
value=«67565» />
Ідея крута, розвивайте!
<input type=«radio» id=«vv67567»
class=«radio js-field-data»
name=«variant[]»
value=«67567» />
Так
<input type=«radio» id=«vv67569»
class=«radio js-field-data»
name=«variant[]»
value=«67569» />
Погана ідея, займіться іншою справою
<input type=«radio» id=«vv67571»
class=«radio js-field-data»
name=«variant[]»
value=«67571» />
Мені пофіг

Проголосувало 29 осіб. Утрималося 12 осіб.


Який з розділів Jii описати в наступній статті?

/>
/>

<input type=«radio» id=«vv67573»
class=«radio js-field-data»
name=«variant[]»
value=«67573» />
Валідація даних в моделях
<input type=«radio» id=«vv67575»
class=«radio js-field-data»
name=«variant[]»
value=«67575» />
HTTP сервер з роутингом і генерацією URL
<input type=«radio» id=«vv67577»
class=«radio js-field-data»
name=«variant[]»
value=«67577» />
Публікація ресурсів (Assets Manager)
<input type=«radio» id=«vv67579»
class=«radio js-field-data»
name=«variant[]»
value=«67579» />
Архітектуру і структурні складові (Додаток, Модуль, Компонент...)

Проголосувало 26 осіб. Утрималося 12 осіб.


Тільки зареєстровані користувачі можуть брати участь в опитуванні. Увійдіть, будь ласка.


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.