2010-11-04 69 views
1

假設您有一個「作者」對象,它有幾本書,並且您想要在模型中構建一些方法。你的基本設置看起來是這樣的:複雜的選擇集,Rails的方式?

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 

現在,我寫了一個基本的select語句對於一些這方面,沿線:

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 

當我只有一個或兩個參數,這是很笨拙,但很容易。但是,由於客戶端要求將更多條件添加到方法中,因此編寫起來越來越乏味。

什麼是最好的「軌道」方式來處理?

謝謝!

+0

我們正在使用這些ActiveRecord對象還是簡單的Ruby類?另外,什麼版本的Rails? – 2010-11-04 16:14:59

+0

它的軌道2.3,它們是ActiveRecord對象。我選擇使用select而不是find的原因是,在大多數情況下,我使用這種方法,我已經拉動了更多書籍。 – 2010-11-04 16:34:47

回答

0

如果你渴望加載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列表未加載,則使用Olives建議的named_scope方法。

0
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 } 
6

範圍,(或「named_scopes」,如果您使用Rails < 3)可能是最好的方法。

以下是對於rails 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參數哈希,那麼你可以有任何數量的你不想做這個功能的選項有很多參數。

+0

現在,這是非常整潔:)這就是我喜歡的,即使當我認爲我知道問題的答案時,有人經常有這樣的更好的方式:) – 2010-11-04 16:57:04

1

首先,您可能希望book_count採用散列options={}並在方法本身中定義關鍵缺省值。這樣,由於客戶需要更多選擇(或決定刪除一些選項),因此不必追蹤項目中的所有呼叫並相應地更改它們。我更喜歡這樣做,但您也可以使用*arguments。通過作爲選項散列的

一個好處是,你根本不通過鍵如果值nil,那麼你可以簡單地找到符合搜索條件的圖書的計數,如下所示:

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

這應該可以正常工作,並允許稍後添加其他規格。只要確保options散列中的鍵匹配您的模型屬性即可。

0

更多的Rails-y方法是使用ActiveRecord內置的find方法從數據庫中獲取這些數據,而不是使用Ruby進行過濾。它會更快,並且代碼會更乾淨。 where方法可以採用屬性和值的散列。 (有關更多信息,請參閱ActiveRecord guide to querying,這是一個很好的介紹)

您是否使用Rails 3?在這種情況下,ActiveRecord的使用變得更加簡單。

像這樣的東西可能工作(雖然我沒有進入軌道,現在所以這可能包含錯誤):

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

這應該由筆者發現所有的書(假設你有一個模型作者和書之間的關聯),你指定的所有條件都是真實的。您可能需要先過濾掉任何nils。 filter將是條件的散列,例如{ :genre => 'Horror', :published => true }

請注意,我使用count而不是sizecount使用SQL計數函數,而不是返回數據,然後用ruby計數。

希望有所幫助。