想象一下这样的架构:
ActiveRecord::Schema.define(:version => 20111119180638) do create_table "bikes",:force => true do |t| t.string "Brand" t.string "model" t.text "description" end end
该数据库已包含几种不同的自行车.我的目标是获得数据库中所有品牌的数组.
这是我的代码:
class Bike < ActiveRecord::Base def Bike.collect_brands temp_brands = Bike.find_by_sql("select distinct brand from bikes") brands = Array.new temp_brands.each do |item| brands.push(item.brand) end brands end end
Ruby大师如何编写代码来实现这一目标?
解决方法
此功能已经存在(请参阅我的答案结尾),但首先,让我们逐步完成您的代码并使其更具惯用性:
首先,每级缩进使用两个空格,而不是四个,而不是八个,而不是标签.使用两个空格.这不是个人偏好,这是Ruby社区中非常强大的惯例,如果您打算参与,则非常需要.
接下来,在Ruby中几乎没有充分的理由使用这种模式:
brands = Array.new temp_brands.each do |item| brands.push(item.brand) end
当您想通过将一些代码应用于输入数组中的每个值时,将一个数组转换为另一个数组(实际上,一个Enumerable为另一个Enumerable)时,请使用map
或collect
(这是同义词):
brands = temp_brands.map { |item| item.brand }
接下来,您可以利用symbol#to_proc
使上述代码更清晰:
brands = temp_brands.map &:brand
对于没有经验的人来说这看起来很奇怪,但是一旦你习惯使用map和&:field就会更清楚.一点点经验会使这行代码的意图非常明显:它将品牌方法应用于数组中的每个元素,它完全等同于之前的{| item | item.brand}版本.
现在,您的整个方法可以变得非常简单:
def Bike.collect_brands Bike.find_by_sql("select distinct brand from bikes").map &:brand end
内联选择/不同的sql有点难看,特别是因为ActiveRecord已经让我们select specific fields选择了,并使用uniq使结果不同:
def Bike.collect_brands Bike.select(:brand).uniq.map &:brand end
作为最后的迭代,我们可以使用pluck
而不是map来仅从我们感兴趣的结果中提取字段.但是,因为pluck实际上修改了生成的sql以仅包括被提取的字段,所以我们可以省略select (:品牌)部分,我们的代码归结为包含两个链式方法的令人难以置信的短单行:
def Bike.collect_brands Bike.uniq.pluck(:brand) end
请注意,顺序很重要,因为pluck总是返回一个数组,而不是为其他方法链接准备好的ActiveRecord关系. Bike.pluck(:品牌).uniq将从每个记录(从自行车中选择品牌)中选择品牌,然后在Ruby中将阵列减少为独特的项目.可能是非常昂贵的操作.
就是这样,Bike.uniq.pluck(:品牌).作为一名C程序员,你会发现你习惯用小循环做的许多重复性任务实际上已经通过语言本身或支持库来解决.一旦你学会了编写惯用的Ruby和Rails代码,你不写的代码量就会非常令人惊讶.