Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thinking in Ruby

I'm currently busy learning Ruby and Rails, and since I have a background in C-based languages, some concepts of Ruby are new and somewhat alien. Especially challenging for me is adapting to the "Ruby-way" of approaching common problems, hence I often find myself coding C in Ruby, which is not what I'm trying to achieve.

Imagine having a schema like this:

ActiveRecord::Schema.define(:version => 20111119180638) do
    create_table "bikes", :force => true do |t|
        t.string   "Brand"
        t.string   "model"
        t.text     "description"
    end
end

The database already contains several different bikes. My goal is to get an array of all brands represented in the database.

Here is my code:

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

How would a Ruby guru write code to achieve this?

like image 922
KernelPanic Avatar asked Oct 23 '12 16:10

KernelPanic


People also ask

Is Ruby hard to master?

And no, it's not hard to learn at all! Between its thriving community and its straightforward workflow, Ruby on Rails may be one of, if not THE, most beginner-friendly frameworks in existence.

Should I learn Ruby in 2022?

Although way behind main contenders, such as PHP or Python, Ruby still makes the cut for the 20 most popular programming languages list in 2022. The 2022 edition of Stack Overflow Annual Developer Survey also places RoR in a similar spot.

How many hours does it take to learn Ruby?

With our Learn Ruby and Learn Ruby on Rails courses, it will take about 10 hours to get through the material. However, once you're done, you'll want to spend time practicing on your own and building projects to really master the language.

How long does it take to become proficient in Ruby?

If you don't know Ruby and have experience with other similar web frameworks, then probably 3-6 weeks. If you are a new programmer, plan on 3-4 months to really get a grip on everything conceptually as well.


1 Answers

tl;dr: Your entire method can be replaced with Bike.uniq.pluck(:brand).


This functionality already exists (see the end of my answer), but first, lets step through your code and make it more idiomatic:

First and foremost, use two spaces per level of indentation, not four, not eight, and not tabs. Use two spaces. This is not personal preferences, this is an extremely strong convention within the Ruby community and pretty much required if you intend to participate.

Next, there almost never a good reason to use this pattern in Ruby:

 brands = Array.new
 temp_brands.each do |item|
   brands.push(item.brand)
 end

When you want to translate one array into another array (really, one Enumerable into another Enumerable) by applying some code to each of values in the input array, use map or collect (which are synonyms):

brands = temp_brands.map { |item| item.brand }

Next, you can take advantage of symbol#to_proc to make the above code a little clearer:

brands = temp_brands.map &:brand 

This will look strange to the uninitiated, but it is clearer once you're used to working with map and &:field. A little bit of experience will make the intent of this line of code very obvious: It's applying the brand method to each element in the array, and it's exactly equivalent to the previous { |item| item.brand } version.

Now, your entire method can become a pretty simple one-liner:

def Bike.collect_brands
  Bike.find_by_sql("select distinct brand from bikes").map &:brand
end

That inline select/distinct SQL is kind of ugly, especially since ActiveRecord already lets us select specific fields with select, and make the results distinct using uniq:

def Bike.collect_brands
  Bike.select(:brand).uniq.map &:brand
end

As a final iteration we can use pluck instead of map to pull only the fields out of the results that we're interested in. But, because pluck actually modifies the SQL being generated to only include the fields being plucked, we can omit the select(:brand) portion, and our code boils down to an incredibly short single line containing two chained methods:

def Bike.collect_brands
  Bike.uniq.pluck(:brand)
end

Note that the order is important because pluck always returns an array, not an ActiveRecord relation ready for additional method chaining. Bike.pluck(:brand).uniq would select the brand from every record (select brand from bikes) and then, in Ruby, reduce the array to the unique items. Potentially a very expensive operation.

And that's it, Bike.uniq.pluck(:brand). As a C programmer, you'll find that many of repetitive tasks you're used to doing with small loops are practically already solved for you by the language itself or by supporting libraries. The amount of code you don't write can be very surprising, once you've learned to write idiomatic Ruby and Rails code.

like image 158
meagar Avatar answered Sep 19 '22 05:09

meagar