Выполнение update_all с объединениями в Rails

Как раз так, что абстракция Rails от необработанного SQL приводит меня в замешательство. В MySQL я мог бы сделать это:

UPDATE FROM tasks AS t 
LEFT JOIN projects as p 
ON t.project_id = p.id 
SET t.invoice_id = 7
WHERE p.organization_id == 42
AND t.invoice_id IS NULL

Как я могу сделать это в Rails 3.0.1 с нетерпением загрузки? Я пробовал все следующее:

Tasks.joins(:project).where('projects.organization_id' => 42, :invoice_id => nil).update_all( :invoice_id => 7 )

И все варианты вышеизложенного. Все либо давали ошибки, либо ничего не нашли.

Затем я попытался использовать scope:

Task.scope :find => {:joins => :project, :conditions => ["projects.organization_id == ? AND invoice_id IS NULL", @organization.id] } do
  Task.update_all :invoice_id => @invoice.id
end

Это дало мне ошибку undefined method 'to_sym' for #<Hash:0x1065c6438>.

Я потратил слишком много часов на это, просто чтобы воспроизвести простой SQL-запрос. Пожалуйста, помогите!


РЕДАКТИРОВАТЬ: Временное плохое решение, чтобы обойти n + 1:

task_ids = Task.select('tasks.id').joins(:project).where('projects.organization_id' => @organization.id, :invoice_id => nil).collect{|t| t.id}
Task.update_all ['invoice_id = ?', @invoice.id], ["id in (#{task_ids.join(',')})"]

Ответ 1

"ОБНОВЛЕНИЕ ОТ" не является стандартным SQL, поэтому неудивительно, если он не поддерживается непосредственно Active Record. Тем не менее, Active Record дает вам возможность обойти свои абстракции и просто выпускать прямой SQL, в те моменты, когда вы должны делать что-то, что не поддерживает. Внутри модели:

sql = "UPDATE FROM tasks AS t 
LEFT JOIN projects as p 
ON t.project_id = p.id 
SET t.invoice_id = 7
WHERE p.organization_id == 42
AND t.invoice_id IS NULL"
connection.update_sql(sql)

ActiveRecord:: Base также имеет метод select_by_sql, который позволяет вашим нестандартным операторам выбора возвращать регулярные экземпляры активной активной записи.

Ответ 2

Я считаю, что по крайней мере @rails 3.0.8 и ARel 2.0.10 мы не могли напрямую генерировать UPDATE FROM, но получить тот же результат можно, разрешив соединение в качестве подзапроса, например

Task.where(:invoice_id=>nil).
where(:project_id=>Project.where(:organization_id=>42).collect(&:id)).
update_all(:invoice_id => 7)

Это генерирует SQL как:

UPDATE "tasks"
SET "invoice_id" = 7 
WHERE "invoice_id" IS NULL AND "project_id" IN (1,2,3);
-- assuming projects 1,2,3 have organization_id = 42

Из нашего, что разрешает подзапрос в Rails не в SQL. Чтобы сгенерировать подвыбор в SQL, вы можете смешать немного Ареля следующим образом:

t = Task.arel_table
p = Project.arel_table
Task.where(:invoice_id=>nil).
where(
  t[:project_id].in(
    p.where(
      p[:organization_id].eq(42)
    ).project(p[:id])
  )
).update_all(:invoice_id => 7)

Что генерирует sql, как это:

UPDATE "tasks"
SET "invoice_id" = 7 
WHERE "invoice_id" IS NULL 
AND "project_id" IN (
SELECT "projects"."id" FROM "projects" 
WHERE "projects"."organization_id" = 42
);

Здесь есть чистый способ ARel, но синтаксис UpdateManager сильно недооценен

Ответ 3

Это чисто рубины/рельсы, он не должен быть помечен как SQL -

Единственная информация о SQL, которую вы могли бы получить, - это начать с другого синтаксического эквивалента вместо "обновления из", который не является стандартным, например, например (это я бы тоже не сделал, но эй, я не использую ruby ​​/рельсы).

UPDATE tasks t
SET t.invoice_id=7 
WHERE 
t.invoice_id IS NULL 
AND 
(SELECT 
p.organization_id 
FROM tasks t2 
LEFT JOIN projects p 
ON t.project_id=p.id 
WHERE t2.id=t.id)=42

Ответ 4

Я считаю, что следующие

UPDATE FROM tasks AS t 
LEFT JOIN projects as p 
ON t.project_id = p.id 
SET t.invoice_id = 7
WHERE p.organization_id == 42
AND t.invoice_id IS NULL

может быть записан как следующий запрос Arel:

Tasks.include(:projects).where("projects.organization_id = ?", 42).where("tasks.invoice_id IS NULL").update_all("tasks.invoice_id = ?", 7)

Это предполагает, что у вас есть правильная связь между задачами и проектами.