понедельник, 22 июня 2009 г.

Связи в моделях. Один ко многим

В предыдущей статье обсуждалась проблема связывания 2 моделей, как "1 к 1". Наша сегодняшняя тема - как связать модели один ко многим.

На самом деле, все не сложнее чем связь 1 к 1, потому чтобы не вдаваться в детали, сразу приведу пример:

class Order < ActiveRecord::Base
   belongs_to :user
end

class User < ActiveRecord::Base
   has_many :orders
end

Здесь описываем связь, когда каждый пользователь может иметь несколько заказов.

Как и в предыдущем примере, связь belongs_to в модели Order показывает что каждый ее экземпляр, может относится только к одному экземпляру модели пользователей (User).

В модели User есть новое для нас условие связи has_many :orders , которое, буквально дословно, означает, что каждый экземпляр класса (модели) User - имеет несколько экземпляров класса (модели) Order.

Вот впринципе и все описание. Теперь осталось рассмотреть несколько примеров использования :

Пример только в целях демонстрации, не используйте его для подсчета суммы:
user = User.find(id)
total = 0;
user.orders.each do |order|
   total += order.price
end

Правильно посчитать сумму можно так:
total = user.orders.sum(:price)

Мы легко можем оперировать свойством orders в цикле, где множественное число имени свойства показывает нам, что мы имеем дело с коллекцией экземпляров order, для конкретного экземпляра user.

Рассмотрим еще один пример, который продемонстрирует, как можно добавлять новые заказы пользователю, а также то, как их при этом создавать даже "на лету":

new_order = Order.create(:price => 150.00)
user.orders << new_order

или еще проще:

user.orders << Order.create(:price => 100.00)

Где "<<" - операция добавления нового значения в массив (коллекцию)

На первый взгляд может показаться, что Рельса не очень гибкая и довольно громоздкая. Но на самом деле все не так - после того, как я опишу все связи моделей, я посвящу несколько статей нестандартным способам описания связей.

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

пятница, 19 июня 2009 г.

Связи в моделях. Один к одному

Я давно хотел написать серию публикаций о связях в моделях, все не доходили руки. Этой статьей я начну серию статей которая раскроет суть взаимодействия моделей в Ruby on Rails.
Связь один к одному
Если у читателей был опыт разработки с использованием БД, то вероятно вам уже известно, как создавать SQL запросы на выборку из разных таблиц. В Ruby on Rails все выглядит немного более привлекательно. Так как Active Record является полноценной ORM, то вполне понятно, что работа со связанными таблицами может быть гораздо удобнее.

Как уже вы знаете, каждой таблице в БД, в ROR(ActiveRecord) сопоставляет одноименный обьект (модель), правда стоит учитывать, что имя таблицы должно быть во множественном числе, в отличии от имени обьекта(модели). Также для доступа к каждому полю таблицы существует одноименный метод в модели (Например таблица users содержит поле name, то в модели оно доступно, как obj.name, где obj - объект класса User). Потому и говорят что таблица "мапится" моделью - что и есть основным принципом ORM. Детальнее тут.

Кроме этого, как уже мною было описано ранее,в публикациях касающихся Модели, она содержит также - множество дополнительных функций, предназначенных для обработки полученных данных (всевозможные выборки тут и детальнее тут, валидация, а также связи между моделями).

Так как вместо таблиц мы используем модели, то нам хотелось бы написать что-то типа такого:
user.address.street - где User и Adress єкземпляры классов соответствующих моделей, а street - свойство экземпляра класса адреса. Заманчиво, не правда ли?

Для того, чтобы написать подобное, использем SQL:

SELECT addresses.name
FROM addresses INNER JOIN users on users.id = addresses.user_id
нам бы пришлось выполнить запрос и достать из него нужное поле... Как по мне, то немного неудобно.

Так как же, все-таки, изменить модели, чтобы мы могли так ими оперировать?
Именно это и является темой данной статьи. Давайте рассмотрим запрос на SQL более детально, так как нам надо выяснить для себе некоторые моменты.

Для того, чтобы связать две таблицы как "1 к 1", нам необходимо выяснить для себя - какая таблица является родительской, а какая подчиненной.

Оказывается, это довольно-таки просто: рассмотрим 2 строку запроса, а именно условие обьеденения
users.id = address.user_id
Таблица Addresses содержит первичный ключ таблицы Users, что автоматически делает ее подчиненной.

Сформулируем словами связи между таблицами: таблица Users содержит таблицу Addresses с одной стороны, таблица Addresses относится к таблице Users - с другой стороны. Все вполне логично. Теперь посмотрим, как все выше сказанное реализовать в моделях RoR:

class Address < ActiveRecord::Base
   belongs_to :user
   # дальше идет код который описывает управление данными модели
end
class User < ActiveRecord::Base
   has_one :address
   # дальше идет код который описывает управление данными модели
end

В самом начале описания модели, мы указываем как она связана с другими моделями. Для реализации связи "1 к 1", достаточно "сказать" модели User - что она has_one (содержит один) Address, а Address в свою очередь belongs_to(содержит) User. Вот и все!

Что мы получили в результате
С этого момента модели являются связанными между собой, и при построении запросов типа: user.address.street ActiveRecord автоматически будет добавлять связи.

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

Имеем полное право вычитать адресные данные пользователя:

User.address.find(:all)

street = User.find_by_id(id).address.street