Bắt đầu từ con số không với Ruby?

2120

“Ruby có ngoại hình đơn giản, nhưng có tâm hồn rất phức tạp giống như con người chúng ta vậy.” – Matz, cha đẻ của ngôn ngữ lập trình Ruby.

Vì sao bạn nên học Ruby?

Đối với tôi, Ruby là một ngôn ngữ đẹp. Bạn sẽ thấy việc viết code trong Ruby rất mượt mà và tự nhiên.

Mặt khác, một trong những nguyên nhân khiến cho Ruby nổi tiếng nằm ở Rails: một framework được dùng bởi Twitter, Basecamp, Airbnb, Github, và nhiều công ty khác.

Nguồn gốc

Ruby là một ngôn ngữ lập trình open source vô cùng mạnh mẽ với sự đơn giản mà hiệu quả. Với các cú pháp đầy thanh lịch giúp cho việc đọc và viết vô cùng dễ dàng.

Hãy bắt đầu với nền tảng của Ruby

Tham khảo thêm các vị trí tuyển dụng Ruby lương hấp dẫn trên TopDev

Variables

Bạn có thể hiểu đơn giản rằng một variable là một từ chứa một giá trị. Đơn giản thế thôi.

Trong Ruby, rất dễ để ta define một variable và cho nó một giá trị. Giả sử bạn muốn cho giá trị là 1 vào một variable với tên gọi cũng là một.

one = 1

Quá dễ đúng không?

two = 2
some_number = 10000

Bạn có thể cho bất kì giá trì gì mình muốn cho variable mà bạn thích. Trong ví dụ trên, variable tên là two chứa giá trị 2 và variable tên là  some_number có giá trị 10,000.

Ngoài integers, ta còn có thể dùng booleans (true/false), strings, symbols, float, và các dạng data types khác.

# booleans
true_boolean = true
false_boolean = false

# string
my_name = "Leandro Tk"

# symbol
a_symbol = :my_symbol

# float
book_price = 15.80

Conditional Statements: Điều khiển Flow

Conditional statements sẽ có nhiệm vụ xem xét tính true/false của một statement. Nếu kết quả là True (Đúng), thì nội dung bên trong sẽ được xử lí.

if true
  puts "Hello Ruby If"
end

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

Bởi vì 2 lớn hơn 1 nên dòng chữ “2 is greater than 1” sẽ được hiện ra.

Else statement sẽ được thực hiện khi ta gặp kết quả False(Sai):

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

Bởi 1 không lớn hơn 2, nên code trong Else statement sẽ được xử lí.

Ngoài ra thì bạn cũng có thể dùng tới elsif statement, vốn kết hợp cả 2 loại trên:

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

Tôi thì lại thích dùng if statement  sau khi code đã được thực hiện:

def hey_ho?
  true
end

puts "let’s go" if hey_ho?

Nhìn rất đẹp mà lại ngắn gọn. Đó là thế mạnh của Ruby.

Tham khảo tuyển dụng ruby on rails lương cao trên TopDev

Looping/Iterator

Trong Ruby, chúng ta có thể iterate bằng nhiều hình thức khác nhau: while, for và each.

Vòng lặp While: Miễn là kết quả của statement vẫn là true, nội dung code nằm trong statement sẽ luôn được thực hiện. Như vậy, Code sẽ in và hiển thị từ số 1 tới 10 trong ví dụ dưới đây:

num = 1

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

Vòng lặp For: Với Statement được cho ra, nội dung code sẽ được thực hiện cho đến khi thõa mãn yêu cầu của statement đó.

for num in 1...10
  puts num
end

Each iterator: Với bất kì array của giá trị, nó sẽ iterate  từng giá trị một.

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

Điểm khác biết giữa For và Each là Each thực hiện chính xác từng giá trị được cho trong khi For sẽ xuất hiện những giá trị không mong muốn nằm ngoài yêu cầu.

# 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)

Array: Collection/List/Data Structure

Giả sử như bạn muốn lưu integer 1  vào một variable. Nhưng giờ thì bạn muốn lưu 2, và rùi 3,4,5….

Vậy có cách nào để có thể lưu những integers mà bạn muốn mà không phải cứ làm cách thủ công như vậy. Ruby sẽ cung cấp giải pháp cho bạn.

Array là một collection dùng để lưu trữ một list các values.

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

Rất là đơn giản, chúng ta tạo ra một array và lưu nó vào my_integer.

Bạn sẽ tự hỏi rằng “làm cách nào để lấy giá trị từ array đó?”. Trong Arrays có một khái niệm gọi là Index. Bắt đầu với index 0 và cứ thể tăng dần lên.

Sử dụng cú pháp Ruby, nó rất đơn giản để hiểu:

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

Giờ nếu bạn muốn lưu strings thay vì integers, đại loại như list tên họ hàng của bạn chẳng hạn:

relatives_names = [
  "Toshiaki",
  "Juliana",
  "Yuji",
  "Bruno",
  "Kaio"
]

print relatives_names[4] # Kaio

Không có gì khác ngoài trừ giờ ta dùng chữ thay cho số.

Giờ khi nói về array data structure, ta ám chỉ việc đụng tới các giá trị trong array đó.

Phương pháp thường thấy dùng để thêm giá trị vào array là push và <<.

Push cực kì đơn giản, như ví dụ sau đây:

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

Phương pháp << thì có chút khác biệt với:

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

Bạn có thể thấy sự khác biệt nằm ở dấu chấm. Có thể nói:

bookshelf << "Hooked"

Cũng chính là

bookshelf.<<("Hooked")

Ruby thật là tuyệt đúng không?

Giờ hãy nói thêm về một loại Data Structure khác.

Hash: Key-Value Data Structure/Dictionary Collection

Chúng ta đều đã biết arrays thực chất chính là array với số. Thế nhưng nếu chúng ta dùng số thứ tự thì sao? Một số data structures có thể dùng số, string, hoặc các dạng type khác. Hash data structure là một trong số đó.

Hash là một collection các cặp key-value:

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

Trong đó, Key sẽ ám chỉ index của value. Vậy để truy cấp vào value của Hash thì ta sẽ thông qua Key.

Sau đây là một hash về tôi với Key là họ tên, nickname, sắc tộc:

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

Trong ví dụ trên, tôi cho in ra mọi thông tin về mình.

Một điều khá tuyệt của hash là chúng ta có thể dùng bất cứ thứ gì làm value. Tôi sẽ thêm key “age” và số tuổi của mình là 24 vào value.

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

Giờ chúng ta sẽ thêm các nhân tố khác vào một hash. Key dẫn tới value chính là đặc điểm làm nên Hash nên việc thêm vào cũng sẽ theo qui luật như vậy.

hash_tk = {
  "name" => "Leandro",
  "nickname" => "Tk",
  "nationality" => "Brazilian"  
}

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

Bạn có thấy việc thêm một giá trị vào hash rất đơn giản đúng không?

Iteration: Vòng lặp thông qua Data Structures

Array iteration thật sự rất đơn giản. Ruby developer thường dùng each iterator:

bookshelf = [
  "The Effective Engineer",
  "The 4 hours work week",
  "Zero to One",
  "Lean Startup",
  "Hooked"
]

bookshelf.each do |book|
  puts book
end

Each iterator ở trên hoạt động bằng cách đi qua từng yếu tố trong array như là một thông số. Trong ví dụ trên, chúng ta sẽ in ra từng yếu tố đó.

Với hash data structure, ta cũng có thể dùng each iterator để đi qua 2 thông số trong cùng một block: key và value.

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

Ta sẽ đặt tên cho hai tham số này là key và value cho khỏi bị nhầm lẫn.

hash_tk = {
  "name" => "Leandro",
  "nickname" => "Tk",
  "nationality" => "Brazilian",
  "age" => 24
}

hash_tk.each do |attribute, value|
  puts "#{attribute}: #{value}"
end

Classes & Objects

Là một ngôn ngữ lập trình object oriented, Ruby dùng class và object.

“Class” là một cách để define objects. Ví dụ như xe ô tô có nhiều loại.

“Objects” có 2 đặc điểm: data và behavior. Ví dụ như ô tô có data về số bánh xe, chiều dài cũng như behavior với khả năng tăng tốc và phanh.

Trong lập trình object oriented, ta gọi data là “attributes”  và behavior là “methods.”

Ruby Object Oriented Programming Mode: On

Sau đây là một cú pháp trong Ruby cho Class:

class Vehicle
end

Ta define Vehicle với class statement và kết thúc bằng end.

Objects chính là đại diện cho class. Ta tạo ra instance bằng phương pháp .new

vehicle = Vehicle.new

Tại đây, vehicle là object (hay instance)  của class: Vehicle

vehicle class có 4 attributes: bánh xe, loại thùng tank, số ghế và vận tốc.

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

Chúng ta đã sử dụng phương pháp initialize (khởi tạo). Với tên gọi khác là constructor bởi khi bạn tạo ra vehicle object, đồng thời ta cũng sẽ define các attributes của nó luôn.

Giả sử bạn rất thích chiếc Tesla Model S và muốn tạo một object như vậy. Bao gồm 4 bánh, là xe điện, 5 chỗ và chạy được với vận tốc tối đa là 250km/hour (155 mph).

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
# => <Vehicle:0x0055d516903a08 @number_of_wheels=4, @type_of_tank="electric", @seating_capacity=5, @maximum_velocity=250>

Vậy là ta đã set up lên Tesla’s attributes. Nhưng truy cập vào bằng cách nào?

Tất nhiên là bằng cách gửi tin tới object để hỏi về chúng.

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

Trong ví dụ trên, ta dùng hai cách number_of_wheels và set_number_of_wheels. Hay còn được biết tới là getter and setter. Đầu tiên là ta lấy một giá trị với `get`, thứ hai ta có thể đặt ra một giá trị với set.

Trong Ruby, ta có thể dùng những phương pháp trên với attr_reader, attr_writer và attr_accessor.

  • attr_reader: áp dụng phương pháp getter
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: áp dụng phương pháp setter
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 # => <Vehicle:0x0055644f55b820 @number_of_wheels=4, @type_of_tank="electric", @seating_capacity=5, @maximum_velocity=250>

# number_of_wheels equals 3
tesla_model_s.number_of_wheels = 3
tesla_model_s # => <Vehicle:0x0055644f55b820 @number_of_wheels=3, @type_of_tank="electric", @seating_capacity=5, @maximum_velocity=250>
  • attr_accessor: áp dụng cả 2 phương pháp trên
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

Ngoài ra ta còn có những phương pháp khác như “make_noise” method

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

Khi ta call thì nó sẽ return với một string là “VRRRRUUUUM”.

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

Encapsulation: Cất giấu thông tin

Encapsulation là cách giới hạn việc truy cập trực tiếp vào objects’ data đồng thời tạo điều kiện cho hoạt động cho data.

Nói cách khác những thông tin bên trong sẽ được giấu kín.

Như vậy, trong Ruby, ta sẽ dùng một phương pháp để truy cập dữ liệu trực tiếp:

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

Như vậy, ta đã áp dụng Person class.

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

Vậy thì để truy cập được nhưng thông tin này thì phải làm sao? call name và age method?

tk.name
> NoMethodError: undefined method `name' for #<Person:0x0055a750f4c520 @name="Leandro Tk", @age=24>

Rõ ràng là không được. Đó là bởi chúng ta phải dùng phương thức truy cập trực tiếp như sau:

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

Inheritance: behaviors và characteristics

Một số các objects  đều có những điểm chung với nhau: Behavior và characteristics.

Hãy thử tưởng tượng một chiếc xe với những attributes như số bánh xe, vận tốc tối đa, etc…

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

Car class đã được implement 🙂

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

Trong Ruby, chúng ta dùng < operator để chỉ định rằng một class được thừa kế từ class khác. Một ElectricCar class sẽ thừa kế từ Car class.

class ElectricCar < Car
end

Đơn giản vậy đấy, ta không cần dùng thêm phương pháp nào bởi class này đã có sẵn thông tin được thừa kế từ Car.

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

Module: Một Toolbox

Chúng ta có thể xem module như một toolbox chứa đựng một set các constants và phương pháp.

Một ví dụ của Ruby module là Math.

Math::PI # > 3.141592653589793

Và phương pháp .sqrt:

Math.sqrt(9) # 3.0

Chúng ta cũng có thể áp dụng module của mình và dùng nó trong class. Ta có một RunnerAthlete class:

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

Và áp dụng module Skill vào để có average_speed

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

Vậy làm sao để thêm module vào cho classes để nó có được tính cách trên (average_speed)? Bạn cứ đơn giản là ghi thẳng vào luôn!

class RunnerAthlete
  include Skill

  def initialize(name)
    @name = name
  end
end

Bạn có thể thấy phần “include Skill”! Giờ thì bạn đã có thể xài phương pháp này trong instance của RunnerAthlete class.

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

Tuy vậy bạn cần phải nhớ những điều sau:

  • module có thể không có instances
  • module có thể không có subclass
  • module được define bằng module

Nguồn: blog.topdev.vn via medium

Xem tin tuyển dụng IT lương cao tren TopDev