Ruby on Rails / [Из песочницы] Многотабличные модели в Ruby on Rails

в 12:04, , рубрики: activerecord, ruby on rails, метки: ,

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

Постановка задачи

Итак, смоделируем себе задачу для наглядности представленного решения. Допустим у нас должна быть модель Product. Графическая структура нашей модели представлена на рисунке:
image

Основой модели является таблица 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


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js