Как стать автором
Обновить

Многотабличные модели в Ruby on Rails

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


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

Итак, смоделируем себе задачу для наглядности представленного решения. Допустим у нас должна быть модель 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
    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 сводится к минимуму. Единственным минус в данного решения — это необходимость в постоянном пересоздании этой виртуальной таблицы при изменении имени или удалении столбца.

На этом все. Надеюсь мой пост был вам полезен, всего доброго.
Теги:
Хабы:
Всего голосов 11: ↑8 и ↓3+5
Комментарии18

Публикации

Истории

Работа

Ruby on Rails
10 вакансий

Ближайшие события

One day offer от ВСК
Дата16 – 17 мая
Время09:00 – 18:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область