+4

Ôn thi Ruby Gold

Chào các bạn.

Ngắn gọn thì sau khi thi Ruby Silver 2.1 và nắm được syntax của Ruby, mình quyết tâm setup mục tiêu lấy Ruby Gold để nắm sâu hơn về OOP. Vì vậy có cái memo kiêm blog này.

Môi trường

Các biến global đặc biệt đã được định nghĩa

Đây là kiến thức kì quái mà mình không thể tiếp thu nổi và cũng chưa phải dùng bao giờ 😂😂😂😂 😹 Phần này mình thấy quá kinh dị để nêu ra nên xin phép để link để các bạn đọc thêm ở đây https://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/globalvars.html

Còn trong phạm vi của đề thi(hay bỏ văn vở đi là "học tủ") thì chúng ta chỉ cần nhớ tới $0 là tên của file hiện tại đang chạy code, còn $n với n là 1,2,3,.... là phần tử thứ n khớp với điều kiện đã cho. Các biến này hay được hỏi kèm với regex trong Ruby nên phần này cũng sẽ hơi lai regex 1 chút.

%r|(http://www(\.)(.*)/)| =~ "http://www.abc.com/"
$0 # => tên file
$1 # => "http://www.abc.com/"
$2 # => "."
$3 # => "abc.com"
$4 # => nil

command line option

List đầy đủ ở đây: https://ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/options.html

Nhưng trong các option này cái được nhắc đến và giải thích trong sách cụ thể nhất là -l-d.

  • -l sẽ chỉ định path nào load library. Khi dùng -l, các code ruby có liên quan khác bao gồm $LOAD_PATH, requireload
  • -d là chế độ debug

Các thư viện bổ sung

JSON và YAML

  • Có 1 đoạn JSON như sau
require 'json'

json = <<JSON
{
  "price":100,
  "order_code":200,
  "order_date":"2018/09/20",
  "tax":0.8
}
JSON

Khi đó, JSON.loadJSON.parse cùng cho 1 kết quả như nhau

JSON.load json # => {"price"=>100, "order_code"=>200, "order_date"=>"2018/09/20", "tax"=>0.8}
JSON.parse json # => {"price"=>100, "order_code"=>200, "order_date"=>"2018/09/20", "tax"=>0.8}
  • Với YAML, chúng ta cũng sẽ có Hash tương tự nhưng chỉ với load
require 'yaml'

yaml = <<YAML
  sum: 510,
  orders:
    - 260
    - 250
YAML

object = YAML.load # => {"sum"=>510, "orders"=>[260, 250]}

YAML.load sẽ convert abc: def thành {"abc"=>"def"}(Hash) và - 260\n-250 thành [260,250](mảng)

  • Ngược lại, muốn convert từ Hash sang JSON và YAML
    • Với JSON, ta có hash.to_jsonJSON.dump(hash)
    • Với YAML, ta có hash.to_yaml

freeze!!!!

freeze sẽ khoá cứng lại 1 đối tượng, không cho phép các phương thức ghi đè lên đối tượng đó

s = "ab"
s.freeze
s.concat("c") # => RuntimeError: can't modify frozen String

Ví dụ trên là concat đã là chỉnh sửa s từ trong ra nên lỗi Tuy nhiên, nếu dùng dấu + thì hiểu như thể s được chỉnh sửa từ ngoài vào nên sẽ thành abc

s + "c"
# => "abc"

clone và dup

Nhân có freeze thì cũng phải nhắc đến clonedup

clonedup đều là các method dùng để tạo 1 đối tượng giống hệt đối tượng khác, nhưng có khác nhau:

  • clone sẽ copy nguyên trạng thái freeze, taintsingleton của đối tượng
  • dup không copy các trạng thái ấy

Lưu ý cả clonedup đều là copy nông, không phải deep copy(hay tiếng Nhật là 参照先/sanshousaki. Xin lỗi các bạn vì phải note như này. Đây là câu lý thuyết)

Ví dụ minh họa:

original = %w(apple orange banana)
copy     = original.clone

original.map(&:object_id)
# [23506500, 23506488, 23506476]

copy.map(&:object_id)
# [23506500, 23506488, 23506476]

Forwarder

require 'forwardable'

class RecordCollection
  attr_accessor :records
  extend Forwardable
  def_delegator :@records, :[], :record_number
end

Heredoc

Cần phân biệt <<EOS, <<-EOS<<~EOS. <<EOS sẽ kết thúc với EOS bắt đầu từ column 1 của dòng code

    <<EOS
    def hello
      p 'hi'
    end
EOS

<<-EOS sẽ kết thúc với EOS bắt đầu từ column dọc column bắt đầu <<-EOS của dòng code

    <<-EOS
    def hello
      p 'hi'
    end
    EOS

<<~EOS sẽ tương đương với <<-EOS.gsub(/ /, "").

    <<~EOS
    def hello
      p 'hi'
    end
    EOS

tương đương

def hello
  p 'hi'
end

Thư viện tiêu chuẩn

Thời gian

Cộng trừ thời gian

Ở level Silver, chúng ta đã được làm phép tính cộng thời gian như là 1 biến thời gian cộng hay trừ với 1 số sẽ cho 1 bước nhảy du hành tới tương lai hoặc quá khứ 🤣 Vậy nếu thời gian trừ lẫn nhau thì sao?

  • Cùng Date sẽ cho kiểu Rational
  • Cùng Time sẽ cho kiểu Float
  • Cùng DateTime sẽ cho kiểu Rational

Ví dụ

d = Date.today - Date.new(2015,10,1)
p d.class # => Rational
d = Time.today - Time.new(2015,10,1)
p d.class # => Float

Ở đây DateDateTime là trừ chéo được do cùng thư viện chứ Date hoặc DateTime không trừ chéo được với Time đâu nhe các bợn

strptime

Để định dạng thời gian theo 1 format cố định, ta dùng method strptime:

Time.strptime("00000024021993", "%S%M%H%d%m%Y")
=> 1993-02-24 00:00:00 +0900

# Hoặc nhồi trong log kiểu như này
Time.strptime('91/5/18 4:13:00', '%Y/%m/%d %T'){|y| 
  if y > 100 then y
  elsif y >= 69 then y + 1900
  else y + 2000
  end
}
=> 1991-05-18 04:13:00 +0900

Cú pháp

Block, proc và lambda

1 chút lời nói đầu: đây là các câu rất hay bị hỏi nếu phỏng vấn cho các vị trí code Ruby ở 1 số công ty. Nên bài này phần cú pháp, block-proc-lambda sẽ được đặt vị trí trang trọng nhất

Block

Cú pháp Block

1.upto 5 do |x| puts x end # đúng
1.upto(5) do |x| puts x end # cũng đúng
1.upto(5) { |x| puts x } # đúng, nhưng ghi nhớ là bỏ dấu ngoặc đơn không chạy được đâu các bạn nha

yield

yield là method dùng để gọi 1 block ra để dùng lại

def tag(name)
  puts "<#{name}>#{yield}</#{name}>"
end

tag(:p) {"Hello World"} # => <p>Hello World</p>

Proc

Proc thực ra cũng không khác Block lắm. Như kiểu Block có tên để truyền tham số ấy

Proc.call

Nếu như block dùng yield thì ở Proc ta sẽ dùng Proc.call

def tag(name, &block)
  puts "<#{name}>#{block.call}</#{name}>"
end

tag(:p) {"Hello World"} # => <p>Hello World</p>

Lambda

Chút hoài niệm: Lần đầu tiên mình dùng lambda tử tế là để viết scope SQL cho các model. Động tới phần này bỗng kỉ niệm cũ ùa về lâng lâng ghê.

Cách để định nghĩa lambda

hi = lambda do |x|
  puts "Hello, #{x}."
end
hi.call("World")
hi = ->(x) { puts "Hello, #{x}." }
hi.call("World")

Và Lambda cũng dùng method call như Proc

Bài toán

  • Để tag(:p) {"Hello World"} ra kết quả <p>Hello World</p> thì 2 đoạn code sau đúng:
def tag(name)
  puts "<#{name}>#{yield}</#{name}>"
end

def tag(name, &block)
  puts "<#{name}>#{block.call}</#{name}>"
end

Hash và Array

Cái này ở Silver rồi mà sao giờ lên Gold trông nó LẠ LẮM.

Ở level Gold, chúng ta sẽ được giới thiệu tới các kiến thức liên quan tới mảng và hash được truyền như tham số hay biến số kiểu như:

  • *args có kiểu dữ liệu là Array.
  • **args có kiểu dữ liệu là Hash.

1 số bài tập ví dụ:

Bài 1

x, *y = *[0, 1, 2]
p x, y

Ở đây, cụm *[0, 1, 2] tương đương với 0, 1, 2 luôn nên ta có x là kiểu đơn tương ứng phần tử đầu tiên của dãy, còn *y là kiểu array thì tương ứng với các phần tử còn lại. Nên kết quả trả ra là 0 [1, 2]

Bài 2

def hoge # điền gì vào sau hoge
  puts "#{x},#{y},#{params[:z]}"
end
hoge x: 1, z: 3 # => output: 1,2,3

Đáp án là (x: , y: 2, **params) vì với #{x} được truyền bằng 1 thì cú pháp định nghĩa sẽ là x:, y ở đây không được truyền vào nhưng lại có output bằng 2 nên định nghĩa luôn y: 2, còn #{params[:z]} thì tham số truyền vào hàm phải là params, và z: 3 là kiểu Hash nên sẽ phải là **params

Method inject

inject hay reduce là method dùng để kết hợp các phần tử có kiểu dữ liệu enum thông qua các toán tử

# Sum some numbers
(5..10).reduce(:+)                             #=> 45
# Same using a block and inject
(5..10).inject { |sum, n| sum + n }            #=> 45

Như vậy chúng ta có thể bổ sung các cặp method tương đương: map - collect, select - find_all, detect - findinject - reduce.

Ví dụ: để có mảng [1, 4, 9] từ mảng [1, 2, 3]

[1, 2, 3].map{|x| x ** 2} # cách 1
[1, 2, 3].collect{|x| x ** 2} # cách 2
[1, 2, 3].inject([]) {|x, y| x << y ** 2} # cách 3
[1, 2, 3].reduce([]) {|x, y| x << y ** 2} # cách 4

Kiểu dữ liệu số

  • Lấy giá trị 4/5, ta có
p 4/5 # => 0
p 4.0/5 # => 0.8
p 4/5.0 # => 0.8
p 4/5r # => 4/5
  • Phép toán với các kiểu số:
a = 1.0 + 1
a = a + (1/2r)
a = a + (1 + 2i)

Dòng 1: Integer với Float sẽ cho kiểu dữ liệu là Float

Dòng 2: Float với Rational sẽ cho kiểu dữ liệu là Float

Dòng 3: Float với Complex sẽ cho kiểu dữ liệu là Complex ➡️ ồ, vậy ra trong Ruby, Complex là vua của các số cũng như IT là vua của các ngà... à mà thôi

catch-throw

Cú pháp sẽ như sau

catch Object do
  throw Object
end

Ví dụ

a, b = catch(:exit) do
  for x in 1..10
    for y in 1..10
      throw(:exit) if x + y == 10
    end
  end
end

puts a, b # => 1, 9

Tại đây, khi xét x=1, chạy tới y=9 thì code dừng. Do đó, a và b là 1 và 9.

begin-raise-rescue-ensure

Đây là phần cũng tương tự như ở Silver thôi. Nhưng sẽ có thêm ensure là chắc chắn sẽ in ra code và SystemExit không phải là lỗi mà là thoát hệ thống

begin
  exit
rescue RuntimeError
  puts "RuntimeError"
rescue SystemExit
  puts "SystemExit"
ensure
  puts "End"
end
# => SystemExit
# => End

DATA và __END__

Ruby hay được quảng cáo rầm rộ về metaprogramming, tức là có khả năng biến chương trình khác thành input data. Vâng, đây là ví dụ rõ ràng nhất: Lấy chính code Ruby ra làm input đầu vào

while DATA.gets
  puts $_ if $_ =~ /Ruby/
end
__END__
Java programming
Ruby programming
C programming

Kết quả trả về là Ruby programming. Input data sẽ bắt đều từ DATA với __END__

module_eval, instance_eval

Đây cũng là 1 cách metaprogramming trong Ruby

module eval

module A
  B = 42

  def f
    21
  end
end

A.module_eval(<<-CODE)
  def self.f
    p B
  end
CODE

B = 15

A.f => B

Khi dùng module_eval, có 2 cách để truyền method cho 1 module

  • Dùng self
A.module_eval(<<-CODE)
  def self.f
    p B
  end
CODE
  • Định nghĩa module_function
m.module_eval(<<-EOS)
  CONST = "Constant in Module instance"

  def const
    CONST
  end

  module_function :const
EOS

instance_eval

m.instance_eval(<<-EOS)
  CONST = "Constant in Module instance"

  def const
    CONST
  end
EOS

alias

Từ khoá alias sẽ cho phép ta gọi 1 method A như 1 method B khác. Khi đó, gọi B sẽ có toàn bộ các xử lý của A.

def method
  puts "Hello World"
end
alias old_method method
def method
  old_method
  puts "Hello, Ruby World"
end
method

Kết quả trả ra sẽ có cả Hello WorldHello, Ruby World.

sort

Với Sort, có 2 điều cần chú ý:

  • Khi định nghĩa method sort và quy tắc sort của Ruby, ta dùng <=>
class Company
  attr_reader :id
  attr_accessor :name
  def initialize id, name
    @id = id
    @name = name
  end
  def to_s
    "#{id}:#{name}"
  end
  def <=> other
    self.id <=> other.id
  end
end

companies = []
companies << Company.new(2, 'Liberyfish')
companies << Company.new(3, 'Freefish')
companies << Company.new(1, 'Freedomfish')

companies.sort

companies.each do |e|
  puts e
end
# => 2:Liberyfish
# => 3:Freefish
# => 1:Freedomfish

Ở trường hợp trên, sort không mang tính destructive nên mảng được giữ nguyên

Tuy nhiên, câu chuyện với sort! sẽ là khác

1:Freedomfish
2:Liberyfish
3:Freefish

Để sắp xếp theo thứ tự lớn dần theo giá trị, ta dùng self.id <=> other.id. Còn thứ tự nhỏ dần sẽ là other.id <=> self.id

  • Có thể thay thế toán tử <=> bằng -
class Company
  attr_reader :id
  attr_accessor :name
  def initialize id, name
    @id = id
    @name = name
  end
  def to_s
    "#{id}:#{name}"
  end
  def <=> other
    self.id <=> other.id
  end
end

OOP trong Ruby

Đây là cái phần mình muốn ôn lại nhất trong lần đặt mục tiêu thi lần này. OOP có 4 đặc tính: Tính đóng gói, tính kế thừa, tính đa hìnhtính trừu tượng. Chúng ta sẽ cùng xem 4 đặc tính đó được thể hiện thế nào trong Ruby

ancestors

Đây là khi 1 class của Ruby gọi các cụ ra gánh còng lưng =)))

Giỡn chút chơi chứ nghiêm túc ra, đây chính là 1 đặc điểm ở Ruby thể hiện tính kế thừa trong OOP. Chúng ta sẽ thấy được 1 class được kế thừa từ những class nào.

module Foo
end
class Bar
  include Foo
end
class Baz < Bar
  p ancestors
  p included_modules
  p superclass
end
# => [Baz, Bar, Foo, Object, Kernel, BasicObject]
# => [Foo, Kernel]
# => Bar

Ở đây chúng ta có 3 method:

  • ancestors sẽ trả về các class, superclass và các module được dùng theo thứ tự cái đằng trước kế thừa cái đằng sau, bắt đầu từ class con.
  • included_modules trả về các module được sử dụng. Mặc định tất cả các class đều import module Kernel
  • superclass sẽ trả ra class cha của class hiện tại. Ví dụ như class cha của Baz là Bar
    • superclass của Bar là Object
    • superclass của Object là BasicObject

Từ khoá super

Đây là 1 người quen cũ ở level Silver nhưng trong lúc học mình đã không ghi lại trên Viblo.

Đây cũng là đặc điểm ở Ruby thể hiện tính kế thừa trong OOP. super cho phép gọi lại xử lý của 1 method ở class Cha có cùng tên với class con.

class Animal
  def name
    name = "Animal"
  end
end

class Cat < Animal
  def name
    super + ": Cat"
  end
end

cat = Cat.new
puts cat.name

# "Animal: Cat"

Ở đây, tại cat.name, super + ": Cat" sẽ tương đương name = "Animal" + ": Cat"

Ngoài ra, với việc 1 class con định nghĩa lại 1 method được kế thừa từ class cha ở đây cũng chính là thể hiện tính đa hình.

private, public và protected

Nhiều Tính kế thừa quá đúng là hơi chán. Thế nên rất may chúng ta có 1 ví dụ về tính đóng gói

private, publicprotected thể hiện tính đóng gói trong Ruby.

  • private sẽ chỉ cho gọi method ở trong class kể từ lúc định nghĩa

  • protected cũng tương tự như private, nhưng cho phép 1 số class khác kế thừa từ class định nghĩa truy cập thêm(bao gồm cả chính class ấy, kí hiệu là self)

  • public là cho phép method đó được truy cập ở mọi nơi.

Cách dùng: chỉ cần thêm keyword muốn dùng vào và đặt ở cuối class các method muốn đóng gói. Với public thì không cần từ khoá

class A
  def foo
    bar
  end
  
  private
  
  def bar
    p 'FooBar'
  end
end

A.new.foo # => FooBar

Best practice: Thứ tự đặt public ➡️ protected ➡️ private

Chú ý 1:

Đây là đoạn code sẽ xảy ra lỗi

class A
  def foo
    self.bar
  end
  
  private
  
  def bar
    p 'FooBar'
  end
end

A.new.foo # => NoMethodError

Khi có self, các từ khoá dùng được để chạy success sẽ là public, protected hoặc không điền gì.

Chú ý 2:

Có thể định nghĩa lại tính đóng của method ở class con

class A
  private
  
  def bar
    p 'FooBar'
  end
end
class B < A
  public :bar
end
B.new.bar # => "FooBar"

undef

undef sẽ hủy định nghĩa của 1 method trong 1 class

class Root
  def m
    puts "Root"
  end
end
class A <Root
  def m
    puts "A"
  end
end
class B < A
  def m
    puts "Root"
  end
  undef m
end
B.new.m # => NoMethodError
A.new.m # => A

Refine và using

Đây cũng là 1 ví dụ thể hiện tính đa hình của OOP mà Ruby có. Khi sử dụng đến refine 1 Module trong Ruby thì từ khoá đi theo cặp sẽ là using

class C
  def hoge
    puts "A"
  end
end

module M
  refine C do
    def hoge
      puts "B"
    end
  end
end

c = C.new
c.hoge
using M
c.hoge
# =>
# A
# B

attr_accessor, attr_writer & attr_reader

Lại người quen cũ ở Silver này các bạn 🤣 Và ở đây chúng ta sẽ nhớ

  • attr_accessor cho cả quyền đọc lẫn ghi
  • attr_writter chỉ cho quyền ghi
  • attr_reader chỉ cho quyền đọc Do vậy, 3 đoạn code sau xử lý giống nhau
attr_accessor: x
attr_reader: x
attr_writter: x
def x
  @x
end

def x = (x)
  @x = x
end

Biến instance, biến class

Cần chú ý ở đây là với các biến class @@, giá trị ở tất cả các class sẽ chung. Ví dụ

class A
  @@val = 1
end
class B < A
  @@val = 2
end
A.class_variable_get(:@@val) # => 2

Bài tập ví dụ:

class S
  @@val = 0
  def initialize
    @@val += 1
  end
end

class C < S
  class << C
    @@val += 1
  end

  def initialize
    @@val += 1
    super
  end
end

C.new # => 3
C.new # => 5
S.new # => 6
S.new # => 7

p C.class_variable_get(:@@val) # => 7

Instance method và class method

Chúng ta có đoạn code định nghĩa như sau:

class SayHello
  def self.from_the_class
    "Hello, from a class method"
  end

  def from_an_instance
    "Hello, from an instance method"
  end
end

Ở đây, class method chính là from_the_class còn instance method là from_an_instance Sự khác biệt sẽ ở dưới

>> SayHello.from_the_class
=> "Hello, from a class method"

>> SayHello.from_an_instance
=> undefined method `from_an_instance' for SayHello:Class


>> hello = SayHello.new
>> hello.from_the_class
=> undefined method `from_the_class' for #<SayHello:0x0000557920dac930>

>> hello.from_an_instance
=> "Hello, from an instance method"

instance_variable_get

class Foo
  def initialize
    @foo = 1
  end
end

obj = Foo.new
p obj.instance_variable_get("@foo")     #=> 1
p obj.instance_variable_get(:@foo)      #=> 1
p obj.instance_variable_get(:@bar)   

const

const_missing

Đây là 1 method có trong module, chỉ việc lôi ra định nghĩa lại như code dưới đây

class Object
  X = "X"
  def self.const_missing a
    p "#{a}"
  end
end
Y # => "Y"

const trong class

Cũng như tình huống trên, nếu ta định nghĩa lại const_missing trong Object thì có thể lôi const ra dùng trực tiếp. Nhưng nếu ta gọi 1 class khác như Foo thì cần yêu cầu gọi thêm class trước khi gọi hằng số như Foo::Y

class Foo
  X = "X"
  def self.const_missing a
    p "#{a}"
  end
end
Foo::Y # => "Y"

Include, extend, prepend

  • Khi dùng include, method trong module sẽ trở thành instance method:
module M
  def foo
    self.class
  end
end

class C
  include M
end

p C.new.foo # => C
  • prepend: Tác dụng sẽ như include nhưng sẽ có sự đảo thứ tự kế thừa
module Foo
end
class Bar
  prepend Foo
end
p Bar.ancestors
# => [Foo, Bar, Object, Kernel, BasicObject]
  • extend 1 module ở trong class sẽ biến method trong module đó thành class method ở trong class được thừa kế
module M
  def hoge
    puts "hoge"
  end
end

class A
  extend M
end

A.hoge # => M

define_method

Chức năng cũng tương tự như alias bên trên

class Foo
  def foo() p :foo end
  define_method(:bar, instance_method(:foo))
end
Foo.new.bar    # => :foo

method ở Top Level

Method này rất thân thuộc với mọi người

def talk
  puts "Hello"
end

Và nó tương đương với

class Object
  private
  def talk
    puts "Hello"
  end
end

Ảo diệu ko :v Thật vậy chúng ta đều phải có phép thử.

def talk
  puts "Hello"
end

talk # => "Hello"
Object.new.talk # => private method `talk' called for #<Object:....> (NoMethodError)

class Object
  private
  def talk
    puts "Hello"
  end
end

talk # => "Hello"
Object.new.talk # => private method `talk' called for #<Object:....> (NoMethodError)

Như vậy theo Ruby, các method được định nghĩa đều là private method trong class Object

Design pattern: Singleton

Singleton design pattern là 1 pattern được sinh ra nhằm làm cho đối tượng được khởi tạo từ 1 class là độc nhất. Các bạn có thể đọc kĩ hơn tại: https://www.tutorialspoint.com/design_pattern/singleton_pattern.htm

module Singleton

Khi include module Singleton vào trong 1 class, ta sẽ gọi method instance của class và sau đó là method

require 'singleton'

class Message
  include Singleton

  def morning
    'Hi, good morning!'
  end
end

p Message.instance.morning # => 'Hi, good morning!'
p Message.new.morning # => Lỗi nha các bạn
p Message.morning # => Lỗi nha các bạn

singleton_class

Trả về Singleton class của 1 object

class C
end

p C.singleton_class # => #<Class:C>
p C.singleton_class.singleton_class.singleton_class.singleton_class # => #<Class:#<Class:#<Class:#<Class:C>>>>

Code mẫu luyện tập

Phần này được sinh ra sau lần đầu mình thi trượt. Ở trong đề thi đã xuất hiện các phần sử dụng đoạn code như ở trong sách luyện nhưng hỏi khác phần và khi đó 1 số câu mình đã bị tủ đè

Code 1

p ("aaaaaa".."zzzzzz").lazy.select { |e| e.end_with?("f") }.take(3).force

Trong đề thi sẽ dùng các công thức toán nhưng cứ tạm lấy câu này

  • Kết quả câu lệnh trên: 3 phần tử thỏa mãn điều kiện trong block là ["aaaaaf", "aaaabf", "aaaacf"]
  • Chắc chắn phải gọi lazy vì với số phần tử "aaaaaa".."zzzzzz" sẽ vô cùng lớn, có thể time out hệ thống
  • Chỗ select có thể thay bằng find_all(Ruby Silver)
  • take(3).force có thể thay bằng first(3)
  • Nếu bỏ force thì kết quả sẽ thành #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: "aaaaaa".."zzzzzz">:select>:take(3)>

Code 2:

class Object
  private
  def foo
    p 'Hello, world!'
  end
end

def bar
  foo
end

Tổng kết

Well, ngoài mục tiêu OOP của mình có vẻ phần nào đã đạt được thì mình cũng học được khá nhiều kiến thức về Ruby mà có khả năng support được cho các mục đích khác như viết serverless function thông qua IO, Fiber hay là Thread. Cảm ơn các bạn đã đọc bài.

Nếu bài viết hay, các bạn có thể donate mình ở https://www.buymeacoffee.com/gryqhon


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí