我目前正在忙于学习Ruby和Rails,因为我有基于C语言的背景,所以Ruby的一些概念是新的,有点陌生。对我来说特别具有挑战性的是适应接近常见问题的“Ruby方式”,因此我经常发现自己在Ruby中编写C语言,这不是我想要实现的。
想象一下这样的架构:
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大师如何编写代码来实现这一目标?
答案 0 :(得分:26)
tl; dr:您的整个方法可以替换为Bike.uniq.pluck(:brand)
。
此功能已经存在(请参阅我的答案结尾),但首先,让我们逐步完成您的代码并使其更具惯用性:
首先,每个缩进级别使用两个空格,而不是四个,而不是八个,而不是标签。使用两个空格。这不是个人偏好,这是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
,就会更清晰。一点点经验会使这行代码的意图非常明显:它将brand
方法应用于数组中的每个元素,它完全等同于之前的{ |item| item.brand }
版本。
现在,您的整个方法可以变得非常简单:
def Bike.collect_brands
Bike.find_by_sql("select distinct brand from bikes").map &:brand
end
内联选择/不同的SQL有点难看,特别是因为ActiveRecord已经允许我们select specific fields使用select
,并使用uniq
使结果不同:
def Bike.collect_brands
Bike.select(:brand).uniq.map &:brand
end
作为最后一次迭代,我们可以使用pluck
代替map
来仅从我们感兴趣的结果中提取字段。但是,因为pluck
实际修改了SQL生成为只包含被拔除的字段,我们可以省略select(:brand)
部分,我们的代码归结为包含两个链式方法的极短的单行:
def Bike.collect_brands
Bike.uniq.pluck(:brand)
end
请注意,顺序很重要,因为pluck
始终返回一个数组,而不是为其他方法链接准备的ActiveRecord关系。 Bike.pluck(:brand).uniq
会从每条记录(select brand from bikes
)中选择品牌,然后在Ruby 中选择,将数组缩减为唯一的项目。可能是非常昂贵的操作。
就是这样,Bike.uniq.pluck(:brand)
。作为一名C程序员,你会发现你习惯用小循环完成的许多重复性任务实际上已经由语言本身或支持库解决了。一旦你学会编写惯用的Ruby和Rails代码,你不写的代码量就会非常令人惊讶。