Сложные наборы выбора, путь Rails? - PullRequest
1 голос
/ 04 ноября 2010

Допустим, у вас есть объект "Автор", у которого есть несколько книг, и вы хотите встроить некоторые методы в модель. Ваша базовая настройка выглядит примерно так:

class Author
  def book_count(fiction = nil, genre = nil, published = nil)
  end
end

Для каждого аргумента у вас есть несколько способов работы:

fiction = true #retrieve all fiction books
fiction = false #retrieve all nonfiction
fiction = nil #retrieve books, not accounting for type

genre = nil #retrieve books, not accounting for genre
genre = some_num #retrieve books with a specific genre id

published = true #retrieve all published
published = false #retrieve all unpublished
published = nil #retrieve books, not accounting for published

Теперь я написал для некоторых из них базовый оператор выбора, например:

if published == true
  return self.books.select{ |b| b.published == true }.size
elsif published == false
  return self.books.select{ |b| b.published == false}.size
else
  return self.books.size
end

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

Каким будет лучший способ "рельсов" справиться с этим?

Спасибо!

Ответы [ 5 ]

6 голосов
/ 04 ноября 2010

scopes (или "named_scopes", если вы используете Rails <3), вероятно, лучший способ сделать это. </p>

Следующее относится к рельсам 3, но это может быть сделано с незначительными синтаксическими изменениями. Выможет создать кучу областей в вашей модели.Т.е.

scope :with_genre, lambda {|genre| where(:genre => genre) unless genre.nil?}
scope :published, lambda{|published| where(:published => published) unless published.nil?}
scope :fiction,, lambda{|fiction| where(:fiction => fiction) unless fiction.nil?}

и т. Д.

Затем, когда вам нужно получить к ним доступ, вы можете делать такие вещи, как

def book_count(..)
  self.books.with_genre(genre).published(published).fiction(fiction).size
end

Кроме того, вы можете просто сделать параметр book_count хэшем,тогда вы можете иметь любое количество опций, которые не нужны, чтобы у функции было много параметров.

1 голос
/ 04 ноября 2010

Во-первых, вы можете book_count взять хэш options={} и определить ключевые значения по умолчанию в самом методе.Таким образом, поскольку клиенту требуется больше параметров (или он решает удалить некоторые из них), вам не нужно отслеживать все вызовы в вашем проекте и изменять их соответствующим образом.Я предпочитаю делать так, но вы также можете использовать *arguments.

Одно из преимуществ передачи в качестве хэша опций заключается в том, что вы просто не передаете ключи, если значения nil, тогда выможно просто найти количество книг, которые соответствуют вашим критериям поиска, следующим образом:

return self.books.find(:all, :conditions => options).count

Это должно работать нормально и допустить добавление дополнительных спецификаций позже.Просто убедитесь, что ключи в хеше options соответствуют атрибутам вашей модели.

0 голосов
/ 05 ноября 2010

Если вы уже загружены books, вы можете попробовать это:

def book_count(options = {})
  books.select{|b| options.all?{|k, v| v.nil? || b.send(key) == v} }.size
end

Теперь вы можете совершать звонки, такие как

author.books.book_count(:genre => "foo", :fiction => true)

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

Если список books загружен не очень активно, используйте подход named_scope, предложенный Olives .

0 голосов
/ 04 ноября 2010

Более подходящим способом Rails-y было бы использование встроенных в ActiveRecord методов поиска, чтобы получить их из базы данных, а не фильтровать их в Ruby.Это будет быстрее, и код будет чище.Метод where может принимать хэш атрибутов и значений.(см. руководство ActiveRecord по запросу для получения дополнительной информации, это хорошее введение)

Используете ли вы Rails 3?В этом случае ActiveRecord стало еще проще в использовании.

Нечто подобное может сработать (хотя у меня сейчас нет доступа к рельсам, так что это, вероятно, содержит ошибки):

class Author
    def book_count( filter )
        Book.find_by_author( self ).where( filter ).count
    end
end 

Это должно найти все книги этого автора (при условии, что у вас есть модельная связь между Автором и Книгой), где все указанные вами условия выполняются.Возможно, вам придется сначала отфильтровать нули.filter будет хешем таких условий, как { :genre => 'Horror', :published => true }.

Обратите внимание, что я использую count вместо size.count использует функцию подсчета SQL вместо того, чтобы возвращать данные и затем считать их в ruby.

Надеюсь, это поможет.

0 голосов
/ 04 ноября 2010
if published.nil?
  return books.size
else
  return books.count{ |b| b.published == published }
end

или

if published.nil?
  return books.size
else
  return books.map(&:published).count published
end

или

return books.count{ |b| published.nil? || b.published == published }

или

return published.nil? ? books.size : books.map(&:published).count(published)

или

return published.nil? ? books.size : books.count{ |b| b.published == published }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...