Навчання Рубі: від нуля до героя

"Рубін на вигляд простий, але всередині дуже складний, як і наше людське тіло". - Мац, творець мови програмування Ruby

Навіщо вчитися Рубі?

Для мене перша причина полягає в тому, що це прекрасна мова. Природно кодувати, і це завжди висловлює мої думки.

Другою - і головною - причиною є Rails : той самий фреймворк, який використовують Twitter, Basecamp, Airbnb, Github та багато інших компаній.

Вступ / Історія

Ruby - це «Динамічна мова програмування з відкритим кодом, зосереджена на простоті та продуктивності. Він має елегантний синтаксис, який природно читати і легко писати ». - ruby-lang.org

Почнемо з деяких основ!

Змінні

Ви можете думати про змінну як про слово, яке зберігає значення. Просто як це.

У Ruby легко визначити змінну та встановити для неї значення. Уявіть, що ви хочете зберегти число 1 у змінній, яка називається одиницею. Давайте зробимо це!

one = 1

Наскільки це було просто? Ви щойно призначили значення 1 змінній, яка називається one.

two = 2 some_number = 10000

Ви можете призначити значення будь-якій змінній, яку хочете. У наведеному вище прикладі дві змінні зберігають ціле число 2, а some_number - 10000.

Окрім цілих чисел, ми також можемо використовувати логічні значення (true / false), рядки, символи, плаваючі та інші типи даних.

# booleans true_boolean = true false_boolean = false # string my_name = "Leandro Tk" # symbol a_symbol = :my_symbol # float book_price = 15.80

Умовні заяви: Потік контролю

Умовні твердження оцінюють істинні чи хибні. Якщо щось відповідає дійсності, воно виконує те, що всередині оператора. Наприклад:

if true puts "Hello Ruby If" end if 2 > 1 puts "2 is greater than 1" end

2 більше 1, таким чином , ставить код виконується.

Цей оператор else буде виконаний, якщо вираз if хибний:

if 1 > 2 puts "1 is greater than 2" else puts "1 is not greater than 2" end

1 не більше 2, тому код усередині оператора else буде виконаний.

Там також є вислів elsif. Ви можете використовувати його так:

if 1 > 2 puts "1 is greater than 2" elsif 2 > 1 puts "1 is not greater than 2" else puts "1 is equal to 2" end

Одним із способів, що мені дуже подобається писати Ruby, є використання оператора if після коду, який буде виконуватися:

def hey_ho? true end puts "let’s go" if hey_ho?

Це так красиво і природно. Це ідіоматична Рубі.

Цикл / Ітератор

У Ruby ми можемо повторювати стільки різних форм. Я розповім про три ітератори: в той час, для і для кожного.

Під час циклу: Поки твердження істинне, код всередині блоку буде виконуватися. Отже, цей код надрукує число від 1 до 10:

num = 1 while num <= 10 puts num num += 1 end

Для циклу: Ви передаєте змінну num в блок, і оператор for буде повторювати її для Вас. Цей код буде надрукований так само, як і код while: від 1 до 10:

for num in 1...10 puts num end

Кожен ітератор: мені дуже подобається кожен ітератор. Для масиву значень він буде повторювати по одному, передаючи змінну в блок:

[1, 2, 3, 4, 5].each do |num| puts num end

Ви можете запитати, яка різниця між кожним ітератором та циклічним циклом. Основна відмінність полягає в тому, що кожен ітератор підтримує лише змінну всередині ітераційного блоку, тоді як для циклу дозволяє змінній жити за межами блоку.

# for vs each # for looping for num in 1...5 puts num end puts num # => 5 # each iterator [1, 2, 3, 4, 5].each do |num| puts num end puts num # => undefined local variable or method `n' for main:Object (NameError)

Масив: Збірник / Список / Структура даних

Уявіть, що ви хочете зберегти ціле число 1 у змінній. Але, можливо, зараз ви хочете зберегти 2. І 3, 4, 5 ...

Чи є у мене спосіб зберігати всі цілі числа, які я хочу, але не в мільйонах змінних? Рубі має відповідь!

Масив - це колекція, яку можна використовувати для зберігання списку значень (наприклад, цілих чисел). Тож використовуймо його!

my_integers = [1, 2, 3, 4, 5]

Це дуже просто. Ми створили масив і зберегли його в my_integer .

Ви можете запитати: "Як я можу отримати значення з цього масиву?" Чудове запитання. Масиви мають поняття індекс. Перший елемент отримує індекс 0 (нуль). Другий отримує 1 і так далі. Ви зрозуміли ідею!

Використовуючи синтаксис Ruby, легко зрозуміти:

my_integers = [5, 7, 1, 3, 4] print my_integers[0] # 5 print my_integers[1] # 7 print my_integers[4] # 4

Уявіть, що ви хочете зберігати рядки замість цілих чисел, як список імен ваших родичів. Моє буде приблизно таким:

relatives_names = [ "Toshiaki", "Juliana", "Yuji", "Bruno", "Kaio" ] print relatives_names[4] # Kaio

Працює так само, як цілі числа. Приємно!

Щойно ми дізналися, як працюють індекси масивів. Тепер давайте додамо елементи до структури даних масиву (елементи до списку).

Найпоширенішими методами додавання нового значення до масиву є push та <<.

Натиснути дуже просто! Вам просто потрібно передати елемент (Ефективний інженер) як параметр push:

bookshelf = [] bookshelf.push("The Effective Engineer") bookshelf.push("The 4 hours work week") print bookshelf[0] # The Effective Engineer print bookshelf[1] # The 4 hours work week

Метод << трохи відрізняється:

bookshelf = [] bookshelf << "Lean Startup" bookshelf << "Zero to One" print bookshelf[0] # Lean Startup print bookshelf[1] # Zero to One

You may ask, “But it doesn’t use the dot notation like other methods do. How could it be a method?” Nice question! Writing this:

bookshelf << "Hooked"

…is similar to writing this:

bookshelf.<<("Hooked")

Ruby is so great, huh?

Well, enough arrays. Let’s talk about another data structure.

Hash: Key-Value Data Structure/Dictionary Collection

We know that arrays are indexed with numbers. But what if we don’t want to use numbers as indices? Some data structures can use numeric, string, or other types of indices. The hash data structure is one of them.

Hash is a collection of key-value pairs. It looks like this:

hash_example = { "key1" => "value1", "key2" => "value2", "key3" => "value3" }

The key is the index pointing to the value. How do we access the hash value? Using the key!

Here’s a hash about me. My name, nickname, and nationality are the hash’s keys.

hash_tk = { "name" => "Leandro", "nickname" => "Tk", "nationality" => "Brazilian" } print "My name is #{hash_tk["name"]}" # My name is Leandro print "But you can call me #{hash_tk["nickname"]}" # But you can call me Tk print "And by the way I'm #{hash_tk["nationality"]}" # And by the way I'm Brazilian

In the above example I printed a phrase about me using all the values stored in the hash.

Another cool thing about hashes is that we can use anything as the value. I’ll add the key “age” and my real integer age (24).

hash_tk = { "name" => "Leandro", "nickname" => "Tk", "nationality" => "Brazilian", "age" => 24 } print "My name is #{hash_tk["name"]}" # My name is Leandro print "But you can call me #{hash_tk["nickname"]}" # But you can call me Tk print "And by the way I'm #{hash_tk["age"]} and #{hash_tk["nationality"]}" # And by the way I'm 24 and Brazilian 

Let’s learn how to add elements to a hash. The key pointing to a value is a big part of what hash is — and the same goes for when we want to add elements to it.

hash_tk = { "name" => "Leandro", "nickname" => "Tk", "nationality" => "Brazilian" } hash_tk["age"] = 24 print hash_tk # { "name" => "Leandro", "nickname" => "Tk", "nationality" => "Brazilian", "age" => 24 } 

We just need to assign a value to a hash key. Nothing complicated here, right?

Iteration: Looping Through Data Structures

The array iteration is very simple. Ruby developers commonly use the each iterator. Let’s do it:

bookshelf = [ "The Effective Engineer", "The 4 hours work week", "Zero to One", "Lean Startup", "Hooked" ] bookshelf.each do |book| puts book end

The each iterator works by passing array elements as parameters in the block. In the above example, we print each element.

For hash data structure, we can also use the each iterator by passing two parameters to the block: the key and the value. Here’s an example:

hash = { "some_key" => "some_value" } hash.each { |key, value| puts "#{key}: #{value}" } # some_key: some_value

We named the two parameters as key and value, but it’s not necessary. We can name them anything:

hash_tk = { "name" => "Leandro", "nickname" => "Tk", "nationality" => "Brazilian", "age" => 24 } hash_tk.each do |attribute, value| puts "#{attribute}: #{value}" end

You can see we used attribute as a parameter for the hash key and it works properly. Great!

Classes & Objects

As an object oriented programming language, Ruby uses the concepts of class and object.

“Class” is a way to define objects. In the real world there are many objects of the same type. Like vehicles, dogs, bikes. Each vehicle has similar components (wheels, doors, engine).

“Objects” have two main characteristics: data and behavior. Vehicles have data like number of wheels and number of doors. They also have behavior like accelerating and stopping.

In object oriented programming we call data “attributes” and behavior “methods.”

Data = Attributes

Behavior = Methods

Ruby Object Oriented Programming Mode: On

Let’s understand Ruby syntax for classes:

class Vehicle end

We define Vehicle with class statement and finish with end. Easy!

And objects are instances of a class. We create an instance by calling the .new method.

vehicle = Vehicle.new

Here vehicle is an object (or instance) of the class Vehicle.

Our vehicle class will have 4 attributes: Wheels, type of tank, seating capacity, and maximum velocity.

Let’s define our class Vehicle to receive data and instantiate it.

class Vehicle def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity) @number_of_wheels = number_of_wheels @type_of_tank = type_of_tank @seating_capacity = seating_capacity @maximum_velocity = maximum_velocity end end

We use the initialize method. We call it a constructor method so when we create the vehicle object, we can define its attributes.

Imagine that you love the Tesla Model S and want to create this kind of object. It has 4 wheels. Its tank type is electric energy. It has space for 5 seats and a maximum velocity is 250km/hour (155 mph). Let’s create the object tesla_model_s! :)

tesla_model_s = Vehicle.new(4, 'electric', 5, 250)

4 wheels + electric tank + 5 seats + 250km/hour maximum speed = tesla_model_s.

tesla_model_s # =>

We’ve set the Tesla’s attributes. But how do we access them?

We send a message to the object asking about them. We call it a method. It’s the object’s behavior. Let’s implement it!

class Vehicle def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity) @number_of_wheels = number_of_wheels @type_of_tank = type_of_tank @seating_capacity = seating_capacity @maximum_velocity = maximum_velocity end def number_of_wheels @number_of_wheels end def set_number_of_wheels=(number) @number_of_wheels = number end end

This is an implementation of two methods: number_of_wheels and set_number_of_wheels. We call it “getter” and “setter.” First we get the attribute value, and second, we set a value for the attribute.

In Ruby, we can do that without methods using attr_reader, attr_writer and attr_accessor. Let’s see it with code!

  • attr_reader: implements the getter method
class Vehicle attr_reader :number_of_wheels def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity) @number_of_wheels = number_of_wheels @type_of_tank = type_of_tank @seating_capacity = seating_capacity @maximum_velocity = maximum_velocity end end tesla_model_s = Vehicle.new(4, 'electric', 5, 250) tesla_model_s.number_of_wheels # => 4
  • attr_writer: implements the setter method
class Vehicle attr_writer :number_of_wheels def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity) @number_of_wheels = number_of_wheels @type_of_tank = type_of_tank @seating_capacity = seating_capacity @maximum_velocity = maximum_velocity end end # number_of_wheels equals 4 tesla_model_s = Vehicle.new(4, 'electric', 5, 250) tesla_model_s # =>  # number_of_wheels equals 3 tesla_model_s.number_of_wheels = 3 tesla_model_s # =>
  • attr_accessor: implements both methods
class Vehicle attr_accessor :number_of_wheels def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity) @number_of_wheels = number_of_wheels @type_of_tank = type_of_tank @seating_capacity = seating_capacity @maximum_velocity = maximum_velocity end end # number_of_wheels equals 4 tesla_model_s = Vehicle.new(4, 'electric', 5, 250) tesla_model_s.number_of_wheels # => 4 # number_of_wheels equals 3 tesla_model_s.number_of_wheels = 3 tesla_model_s.number_of_wheels # => 3

So now we’ve learned how to get attribute values, implement the getter and setter methods, and use attr (reader, writer, and accessor).

We can also use methods to do other things — like a “make_noise” method. Let’s see it!

class Vehicle def initialize(number_of_wheels, type_of_tank, seating_capacity, maximum_velocity) @number_of_wheels = number_of_wheels @type_of_tank = type_of_tank @seating_capacity = seating_capacity @maximum_velocity = maximum_velocity end def make_noise "VRRRRUUUUM" end end

Коли ми викликаємо цей метод, він просто повертає рядок “VRRRRUUUUM”.

v = Vehicle.new(4, 'gasoline', 5, 180) v.make_noise # => "VRRRRUUUUM"

Інкапсуляція: приховування інформації

Інкапсуляція - це спосіб обмежити прямий доступ до даних та методів об’єктів. У той же час це полегшує роботу з цими даними (методи об'єктів).

Інкапсуляція може використовуватися для приховування членів даних та функції членів ... Інкапсуляція означає, що внутрішнє представлення об’єкта, як правило, приховане від зору поза визначенням об’єкта.

- Вікіпедія

Отже, усе внутрішнє представлення об’єкта приховане ззовні, лише об’єкт може взаємодіяти зі своїми внутрішніми даними.

У Ruby ми використовуємо методи прямого доступу до даних. Подивимось приклад:

class Person def initialize(name, age) @name = name @age = age end end

Ми щойно запровадили цей клас Person. І як ми дізналися, для створення об'єкта-людини ми використовуємо новий метод і передаємо параметри.

tk = Person.new("Leandro Tk", 24)

So I created me! :) The tk object! Passing my name and my age. But how can I access this information? My first attempt is to call the name and age methods.

tk.name > NoMethodError: undefined method `name' for #

We can’t do it! We didn’t implement the name (and the age) method.

Remember when I said “In Ruby we use methods to directly access data?” To access the tk name and age we need to implement those methods on our Person class.

class Person def initialize(name, age) @name = name @age = age end def name @name end def age @age end end

Now we can directly access this information. With encapsulation we can ensure that the object (tk in this case) is only allowed to access name and age. The internal representation of the object is hidden from the outside.

Inheritance: behaviors and characteristics

Certain objects have something in common. Behavior and characteristics.

For example, I inherited some characteristics and behaviors from my father — like his eyes and hair. And behaviors like impatience and introversion.

In object oriented programming, classes can inherit common characteristics (data) and behavior (methods) from another class.

Let’s see another example and implement it in Ruby.

Imagine a car. Number of wheels, seating capacity and maximum velocity are all attributes of a car.

class Car attr_accessor :number_of_wheels, :seating_capacity, :maximum_velocity def initialize(number_of_wheels, seating_capacity, maximum_velocity) @number_of_wheels = number_of_wheels @seating_capacity = seating_capacity @maximum_velocity = maximum_velocity end end

Our Car class implemented! :)

my_car = Car.new(4, 5, 250) my_car.number_of_wheels # 4 my_car.seating_capacity # 5 my_car.maximum_velocity # 250

Instantiated, we can use all methods created! Nice!

In Ruby, we use the < operator to show a class inherits from another. An ElectricCar class can inherit from our Car class.

class ElectricCar < Car end

Simple as that! We don’t need to implement the initialize method and any other method, because this class already has it (inherited from the Car class). Let’s prove it!

tesla_model_s = ElectricCar.new(4, 5, 250) tesla_model_s.number_of_wheels # 4 tesla_model_s.seating_capacity # 5 tesla_model_s.maximum_velocity # 250

Beautiful!

Module: A Toolbox

We can think of a module as a toolbox that contains a set of constants and methods.

An example of a Ruby module is Math. We can access the constant PI:

Math::PI # > 3.141592653589793 

And the .sqrt method:

Math.sqrt(9) # 3.0

And we can implement our own module and use it in classes. We have a RunnerAthlete class:

class RunnerAthlete def initialize(name) @name = name end end

And implement a module Skill to have the average_speed method.

module Skill def average_speed puts "My average speed is 20mph" end end

How do we add the module to our classes so it has this behavior (average_speed method)? We just include it!

class RunnerAthlete include Skill def initialize(name) @name = name end end

See the “include Skill”! And now we can use this method in our instance of RunnerAthlete class.

mohamed = RunnerAthlete.new("Mohamed Farah") mohamed.average_speed # "My average speed is 20mph"

Yay! To finish modules, we need to understand the following:

  • A module can have no instances.
  • A module can have no subclasses.
  • A module is defined by module…end.

Wrapping Up!

We learned A LOT of things here!

  • How Ruby variables work
  • How Ruby conditional statements work
  • How Ruby looping & iterators work
  • Array: Collection | List
  • Hash: Key-Value Collection
  • How we can iterate through this data structures
  • Objects & Classes
  • Attributes as objects’ data
  • Methods as objects’ behavior
  • Using Ruby getters and setters
  • Encapsulation: hiding information
  • Inheritance: behaviors and characteristics
  • Modules: a toolbox

That’s it

Congrats! You completed this dense piece of content about Ruby! We learned a lot here. Hope you liked it.

Have fun, keep learning, and always keep coding!

My Twitter & Github. ☺