复杂的选择集,Rails方式?

时间:2010-11-04 16:07:14

标签: ruby-on-rails ruby activerecord select

假设你有一个“作者”对象,它有几本书,你想要在模型中构建一些方法。您的基本设置如下所示:

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 个答案:

答案 0 :(得分:6)

范围,(或者如果你使用Rails< 3则命名为“named_scopes”)可能是最好的方法。

以下是针对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参数设置为哈希值,然后您可以使用任意数量的选项而不会使该函数具有大量参数。

答案 1 :(得分:1)

首先,您可能希望book_count获取哈希options={}并在方法本身中定义键默认值。这样,由于客户端需要更多选项(或决定删除一些选项),您不必追逐项目中的所有调用并相应地更改它们。我更喜欢这样做,但你也可以使用*arguments

作为选项哈希传递的一个好处是,如果值为nil,您只是不传递密钥,那么您只需找到符合搜索条件的书籍数量,如下所示:

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

这应该可以正常工作,并允许以后添加其他规范。只需确保options哈希中的键与您的模型属性匹配。

答案 2 :(得分: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 }

答案 3 :(得分:0)

更多的Rails-y方式是使用ActiveRecord的内置find方法将这些方法从数据库中取出而不是在Ruby中过滤它。它会更快,代码也会更清晰。 where方法可以获取属性和值的哈希值。 (有关更多信息,请参阅ActiveRecord guide to querying,这是一个很好的介绍)

您使用的是Rails 3吗?在这种情况下,ActiveRecord更容易使用。

这样的东西可能会起作用(虽然我现在无法访问rails,所以这可能包含错误):

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中对其进行计数。

希望有所帮助。

答案 4 :(得分: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方法。