default_scope breaks (update|delete|destroy)_all в некоторых случаях

Я считаю, что это ошибка в Rails 3. Я надеюсь, что кто-то здесь может направить меня в правильном направлении. Код, размещенный ниже, предназначен исключительно для иллюстрации этой проблемы. Надеюсь, это не смущает проблему.

Учитывая, что у меня есть модель Post и модель Comment. Post has_many Комментарии и комментарий принадлежит_ к сообщению.

С установленным по умолчанию параметром default_scope для модели Post, определяющим отношения joins() и where(). В этом случае, где () зависит от joins().

Обычно сообщения не будут зависеть от комментариев. Опять же, я просто хочу привести простой пример. Это может быть любой случай, когда where() зависит от joins().

class Post < ActiveRecord::Base
  has_many :comments, :dependent => :destroy

  default_scope joins(:comments).where("comments.id < 999")
end

class Comment < ActiveRecord::Base
  belongs_to :post, :counter_cache => true
end

Выполнение следующей команды:

Post.update_all(:title => Time.now)

Создает следующий запрос и в конечном итоге выдает ActiveRecord::StatementInvalid:

UPDATE `posts` SET `title` = '2010-10-15 15:59:27'  WHERE (comments.id < 999)

Опять же, update_all, delete_all, destroy_all ведут себя одинаково. Я обнаружил такое поведение, когда мое приложение жаловалось при попытке обновить counter_cache. Который в конечном итоге приводит к update_all.

4 ответа

У меня была и эта проблема, но нам действительно нужно было иметь возможность использовать update_all со сложными условиями в default_scope (например, без стандартной области видимости невозможна загрузка, а вставка именованной области буквально повсюду не доставляет никакого удовольствия). Я открыл запрос на удаление с моим исправлением:

https://github.com/rails/rails/pull/8449

Для delete_all я поднял ошибку, если есть условие соединения, чтобы сделать более очевидным, что вы должны сделать (вместо того, чтобы просто бросить условие соединения и запустить delete_all для всего, вы получите ошибку).

Не уверен, что ребята с рельсов сделают с моим запросом на тягу, но подумал, что это актуально для этого обсуждения. (Кроме того, если вам нужно исправить эту ошибку, вы можете попробовать мою ветку и оставить комментарий к запросу на извлечение.)

Я столкнулся с этим также.

Если у вас есть

class Topic < ActiveRecord::Base
  default_scope :conditions => "forums.preferences > 1", :include => [:forum]
end

и вы делаете

Topic.update_all(...)

это потерпит неудачу с

Mysql::Error: Unknown column 'forums.preferences' in 'where clause'

Обходной путь для этого:

Topic.send(:with_exclusive_scope) { Topic.update_all(...) }

Вы можете исправить это, используя этот код (и требуя его в environment.rb или где-нибудь еще)

module ActiveRecordMixins
  class ActiveRecord::Base
    def self.update_all!(*args)
      self.send(:with_exclusive_scope) { self.update_all(*args) }
    end
    def self.delete_all!(*args)
      self.send(:with_exclusive_scope) { self.delete_all(*args) }
    end
  end
end

конец

Тогда только вы update_all! или delete_all! когда у него есть область по умолчанию.

Вы также можете сделать это на уровне класса, не создавая новые методы, например:

def self.update_all(*args)
  self.send(:with_exclusive_scope) { super(*args) }
end

def self.delete_all(*args)
  self.send(:with_exclusive_scope) { super(*args) }
end

Я не думаю, что я бы назвал это ошибкой. Поведение кажется мне достаточно логичным, хотя и не сразу очевидным. Но я разработал решение SQL, которое, кажется, работает хорошо. Используя ваш пример, это будет:

class Post < ActiveRecord::Base
  has_many :comments, :dependent => :destroy

  default_scope do
    with_scope :find => {:readonly => false} do 
      joins("INNER JOIN comments ON comments.post_id = posts.id AND comments.id < 999")
    end
  end
end

На самом деле я использую рефлексию, чтобы сделать ее более надежной, но вышесказанное делает идею идеальной. Перемещение логики WHERE в JOIN гарантирует, что она не будет применяться в неподходящих местах. :readonly вариант заключается в противодействии стандартному поведению Rails по созданию joinsобъекты только для чтения.

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

Другие вопросы по тегам