Доброго времени суток, дорогой читатель. Ты наверное знаком с популярным web-framework Ruby on Rails. Если нет, то в этом посте ты сможешь найти много интересной и познавательной информации. Одно из его правил – «Одна модель – одна таблица». Следуя ему модель Cat должна брать информацию из таблицы cats, если не указано другое имя. А если наша модель состоит из нескольких, допустим шести, таблиц? Стандартный joins/include тут уже не в помощь.
Постановка задачи
Итак, смоделируем себе задачу для наглядности представленного решения. Допустим у нас должна быть модель Product. Графическая структура нашей модели представлена на рисунке:
Основой модели является таблица items. Она связана одиночными связями с таблицами colors, types, stores и manufactures. В свою очередь последние две связаны с addresses полиморфными связями.
Одним из решений в данном случае может быть создание простенького запроса такого характера:
ActiveRecord::Base.connection.execute('SELECT items.*, colors.name as color, types.name as type, stores.name as store_name ... FROM items LEFT JOIN colors ON items.color_id = colors.id LEFT JOIN types ON items.type_id = types.id LEFT JOIN stores ON items.store_id = stores.id ...
В ответ мы получим коллекцию хешей, с ключами имеющих значения имен столбцов или алиасов. Но такой подход имеет кучу недостатков. Во-первых, нет нормального поиска по значениям. Во-вторых, если записей большое количество, то придется создавать собственную пагинацию. А ведь многие любят Rails за ActiveRecord, библиотеки will_paginate, kaminari, meta_search. Поэтому необходимо связать наш большой запрос с ActiveRecord.
Вспомним про Sql View
Для это нам нужно познакомится, если еще не знакомы, с представлениями в sql. Представление (VIEW) — объект базы данных, являющийся результатом выполнения запроса к базе данных, определенного с помощью оператора SELECT, в момент обращения к представлению. Но также представления можно принять за виртуальную readonly-таблицу. Более детально, на примере mysql, про представлении рассказывают здесь. Для нас же важно, что activerecord посчитает его за таблицу, и предоставит свой мощный потенциал.
Для начала создадим миграцию:
rails g migration AddProducts
В ней напишем следующее:
def up ActiveRecord::Base.connection.execute(' CREATE VIEW products AS SELECT i.id AS id, i.name AS name, i.weight AS weight, i.size AS size, c.name as color, t.name as type, s.name AS store_name, sa.street AS store_street, sa.city AS store_city, sa.country AS store_country, sa.phone AS store_phone, m.name AS manufacture_name, ma.street AS manufacture_street, ma.city AS manufacture_city, ma.country AS manufacture_country, ma.phone AS manufacture_phone FROM items AS i LEFT JOIN colors AS c ON i.color_id = c.id LEFT JOIN types AS t ON i.type_id = t.id LEFT JOIN stores AS s ON i.store_id = s.id LEFT JOIN addresses AS sa ON s.id = sa.addressat_id AND sa.addressat_type = "Store" LEFT JOIN manufactures AS m ON i.manufacture_id = m.id LEFT JOIN addresses AS ma ON m.id = ma.addressat_id AND ma.addressat_type = "Manufacture" ') end def down ActiveRecord::Base.connection.execute('DROP VIEW products ') end
После создадим модель Product, сделав ее readonly:
class Products < ActiveRecord::Base # Prevent creation of new records and modification to existing records def readonly? return true end # Prevent objects from being destroyed def before_destroy raise ActiveRecord::ReadOnlyRecord end end
Теперь мы можем использовать всю мощь activerecord, arel и других библиотек для получения, фильтрации и пагинации нашей комплексной модели.
china_products = Product.where(:manufacture_country => ‘China’).page(2) blue_guitars_in_kiev = Product.where(:color => 'Blue', :type => 'Guitar', :store_city => 'Kiev')
Запросы выполняются быстро(если, конечно, не забыли добавить индексы), код на ruby сводится к минимуму. Единственным минус в данного решения — это необходимость в постоянном пересоздании этой виртуальной таблицы при изменении имени или удалении столбца.
На этом все. Надеюсь мой пост был вам полезен, всего доброго.
Автор: watoosh