用Ruby思考

时间:2012-10-23 16:48:23

标签: ruby-on-rails ruby activerecord

我目前正在忙于学习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大师如何编写代码来实现这一目标?

1 个答案:

答案 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),方法是将一些代码应用于输入数组中的每个值,请使用mapcollect(这些是同义词):

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代码,你写的代码量就会非常令人惊讶。