SQL-паттерн в Rails

июня 30, 2011  |  Published in Ruby on Rails, Ruby on Rails 3, Базы данных  |  1 Comment

Ruby on Rails 3ActiveRecord — просто замечательный инструмент для простых запросов. Тем не менее иногда во имя производительности приходится отказаться от всех преимуществ предоставляемых ActiveRecord и снизойти до использования SQL.

Рассмотрим пример. У вас имеются пользователи и проекты. Пользователи могут принадлежать многим проектам через объединенную таблицу memberships. Какой наш виигрыш? Все пользователи, которые не связаны с проектами. Схема таблицы:


create_table :users, :force => true do |t|
end

create_table :memberships, :force => true do |t|
  t.integer :user_id
  t.integer :project_id
end

create_table :project, :force => true do |t|
end

Если вы не хотите использовать SQl, то вам, должно быть, будет удобно использовать что-то вроде этого (написано с головы, клянусь, это пришло ко мне через 5 секунд):

User.all(:include => :memberships).select {|u| !u.memberships.map(&:project).include?(project_id)}

ой, что я здесь натворил! В любом более-менее серьезном проекте мой маленький код создаст множество ActiveRecord объектов чем сьест большой кусок оперативной памяти. имея достаточно большое количество объектов и маленький сервер приложения, мы можем начать использовать жасткий диск в качестве виртуальной памяти!

Хорошо, хорошо, это был плохой пример. Предположим, что нет подкачки из виртуальной памяти, сборщик мусора в Ruby остается дорогим удовольствием!

Зачем создавать объекты, чтобы потом игнорировать их? Это большое расточительство времени выполнения и ресурсов! Давайта снизойдем до непосредственного взаимодействия с базой данных прри помощи SQL.

sql_in_rails(Я не часто использую SQL в Rails, однако когда я делаю это, то использую скоупы.)

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

class User < ActiveRecord::Base
  ...
  scope :not_a_member_of, lambda { |project_id|
    sanitized_join_condition = sanitize_sql_array(
      ["LEFT OUTER JOIN 'memberships' ON
      'memberships'.'user_id' = #{self.quoted_table_name}.'id' AND
      'memberships'.'project_id' = ?", project_id]
    )

    joins(sanitized_join_condition).
    where("'memberships'.'project_id' IS NULL")
  }
...
end

Давайте разберем пример более подробно:

Помните, мы используем LEFT OUTER JOIN потому, что мы хотим получить список всех пользователей независимо от того имеют ли они запись в таблице связей или нет.

Строка с where занимается фильтрацией всех пользователей с данным project_id оставляя нам всех пользователей, которые не участвуют в проектах.

Оригинал статьи на английском:

Responses

  1. none says:

    июля 20, 2011 at 01:14 (#)

    Почитайте пожалуйста про AREL.
    Не учите использовать SQL когда можно обойтись без него

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.