Доброго времени суток, дорогой читатель. Ты наверное знаком с популярным web-framework Ruby on Rails. Если нет, то в этом посте ты сможешь найти много интересной и познавательной информации. Одно из его правил – «Одна модель – одна таблица». Следуя ему модель Cat должна брать информацию из таблицы cats, если не указано другое имя. А если наша модель состоит из нескольких, допустим шести, таблиц? Стандартный joins/include методы activerecord тут уже не в помощь.
Итак, смоделируем себе задачу для наглядности представленного решения. Допустим у нас должна быть модель Product. Графическая структура нашей модели представлена на рисунке:

Основой модели является таблица items. Она связана одиночными связями с таблицами colors, types, stores и manufactures. В свою очередь последние две связаны с addresses полиморфными связями.
Одним из решений в данном случае может быть создание простенького запроса такого характера:
В ответ мы получим коллекцию хешей, с ключами имеющих значения имен столбцов или алиасов. Но такой подход имеет кучу недостатков. Во-первых, нет нормального поиска по значениям. Во-вторых, если записей большое количество, то придется создавать собственную пагинацию. А ведь многие любят Rails за ActiveRecord, библиотеки will_paginate, kaminari, meta_search. Поэтому необходимо связать наш большой запрос с ActiveRecord.
Для это нам нужно познакомится, если еще не знакомы, с представлениями в sql. Представление (VIEW) — объект базы данных, являющийся результатом выполнения запроса к базе данных, определенного с помощью оператора SELECT, в момент обращения к представлению. Но также представления можно принять за виртуальную readonly-таблицу. Более детально, на примере mysql, про представлении рассказывают здесь. Для нас же важно, что activerecord посчитает его за таблицу, и предоставит свой мощный потенциал.
Для начала создадим миграцию:
В ней напишем следующее:
После создадим модель Product, сделав ее readonly:
Теперь мы можем использовать всю мощь activerecord, arel и других библиотек для получения, фильтрации и пагинации нашей комплексной модели.
Запросы выполняются быстро, в одну транзакцию, код на ruby сводится к минимуму. Единственным минус в данного решения — это необходимость в постоянном пересоздании этой виртуальной таблицы при изменении имени или удалении столбца.
На этом все. Надеюсь мой пост был вам полезен, всего доброго.
Постановка задачи
Итак, смоделируем себе задачу для наглядности представленного решения. Допустим у нас должна быть модель 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
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
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 и других библиотек для получения, фильтрации и пагинации нашей комплексной модели.
blue_guitars_in_kiev = Product.where(:color => 'Blue', :type => 'Guitar', :store_city => 'Kiev')
blue_guitars_in_kiev.each do |product|
puts product.store_name
end
Запросы выполняются быстро, в одну транзакцию, код на ruby сводится к минимуму. Единственным минус в данного решения — это необходимость в постоянном пересоздании этой виртуальной таблицы при изменении имени или удалении столбца.
На этом все. Надеюсь мой пост был вам полезен, всего доброго.