Rails находит условия, когда атрибут не является столбцом базы данных

Я думаю, можно с уверенностью сказать, что всем нравится делать что-то подобное в Rails:

Product.find(:all, :conditions => {:featured => true})

Это вернет все продукты, для которых атрибут "featured" (столбец базы данных) имеет значение true. Но скажем, у меня есть такой метод для продукта:

def display_ready?
     (self.photos.length > 0) && (File.exist?(self.file.path))
end

... и я хочу найти все продукты, для которых этот метод возвращает значение true. Я могу придумать несколько запутанных способов сделать это, но я также думаю, что можно с уверенностью сказать, что мы любим Rails, потому что большинство вещей не запутаны.

Я бы сказал, что это довольно распространенная проблема для меня ... Я должен представить, что хороший ответ поможет многим людям. Любые не грязные идеи?


person tybro0103    schedule 26.08.2010    source источник


Ответы (2)


Единственный надежный способ отфильтровать их — несколько уродливый метод извлечения всех записей и запуска их через выборку:

display_ready_products = Product.all.select(&:display_ready?)

Это крайне неэффективно, особенно если у вас есть большое количество продуктов, которые, вероятно, не будут соответствовать требованиям.

Лучший способ сделать это — иметь кеш-счетчик для ваших фотографий, а также установить флаг при загрузке файла:

class Product < ActiveRecord::Base
  has_many :photos
end

class Photo < ActiveRecord::Base
  belongs_to :product, :counter_cache => true
end

Вам нужно будет добавить столбец в таблицу Product:

add_column :products, :photos_count, :default => 0

Это даст вам столбец с количеством фотографий. Есть способ предварительно заполнить эти счетчики правильными числами в начале вместо нуля, но здесь нет необходимости вдаваться в подробности.

Добавьте столбец для записи флага вашего файла:

add_column :products, :file_exists, :boolean, :null => false, :default => false

Теперь активируйте это при сохранении:

class Product < ActiveRecord::Base
  before_save :assign_file_exists_flag

protected
  def assign_file_exists_flag
    self.file_exists = File.exist?(self.file.path)
  end
end

Поскольку эти два атрибута отображаются в столбцах базы данных, теперь вы можете запрашивать их напрямую:

Product.find(:all, :conditions => 'file_exists=1 AND photos_count>0')

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

person tadman    schedule 26.08.2010
comment
Мой метод display_ready был гипотетическим, но это отличный совет! Я никогда не знал о counter_cache. Кроме того, я полностью забыл этот метод выбора. Спасибо! - person tybro0103; 26.08.2010
comment
Кэширование счетчика очень удобно для таких ситуаций, когда вы хотите иметь дело с отношениями, не вдаваясь в детали. Кроме того, рендеринг некоторых ваших флагов в столбцах базы данных делает выбор очень простым, и не так уж сложно написать задачу rake для исправления флагов, если они не синхронизированы. - person tadman; 26.08.2010

Вам нужно сделать двухуровневый выбор:

1) Выберите все возможные строки из базы данных. Это происходит в БД.

2) В Ruby выберите допустимые строки из всех строк. Например

possible_products = Product.find(:all, :conditions => {:featured => true})
products = possible_products.select{|p| p.display_ready?}

Добавлен:

Or:

products = Product.find(:all, :conditions => {:featured => true}).select {|p|
               p.display_ready?}

Второй выбор — это метод выбора объекта Array. Select — очень удобный метод, наряду с обнаружением. (Detect происходит от Enumerable и смешивается с Array.)

person Larry K    schedule 26.08.2010
comment
Я знаю, что вы были первым, кто предложил метод select, который мне был нужен, но tadman просто дал отличный совет. - person tybro0103; 26.08.2010
comment
Без проблем. Спасибо за комментарий. С Уважением. - person Larry K; 26.08.2010