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?
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With