Author Archives: Phở Code

Ruby – Kiểu dữ liệu – Phần 2

Trong phần này chúng ta tiếp tục tìm hiểu về các kiểu dữ liệu của Ruby.

Số hữu tỉ – Rational

Số hữu tỉ là số có thể biểu diễn dưới dạng phân số. Sử dụng số hữu tỉ sẽ giúp tránh các lỗi về làm tròn số vì chúng ta có thể biểu diễn chúng dưới dạng phân số. Ruby có lớp Rational hỗ trợ làm việc với số hữu tỉ. Một số đối tượng trong Ruby có phương thức to_r để chuyển một số thành số hữu tỉ.

puts 5.to_r
puts "43".to_r
puts 1.5.to_r

p Rational 0
p Rational 2/5.0
p Rational 1.5

Trong đoạn code trên chúng ta thực hiện một số thao tác với số hữu tỉ.

puts 5.to_r

Chúng ta có thể chuyển số nguyên 5 sang 5/1 bằng phương thức to_r.

p Rational 1.5

Hoặc chúng ta có thể tạo ra từ lớp Rational.

5/1
43/1
3/2
(0/1)
(3602879701896397/9007199254740992)
(3/2)

Đối tượng nil

Trong C++, Java… có đối tượng NULL thì trong Ruby chúng được gọi là nil. Đây là một đối tượng mô tả giá trị “không có”, ý nói một biến không có giá trị gì cả. nil là một đối tượng tĩnh, tức là trong Ruby chỉ có một đối tượng nil duy nhất giống như đối tượng true hay false vậy, nil được tạo ra từ lớp NilClass.

puts nil
p nil

p $val

p $val1 == $val2

Đoạn code trên thực hiện một số thao tác với nil.

puts nil
p nil

Phương thức puts sẽ in chuỗi rỗng của đối tượng nil, trong khi phương thức p lại in chuỗi "nil" ra màn hình.

p $val

Nếu chúng ta in một biến chưa có giá trị thì Ruby sẽ hiển thị là nil.

p $val1 == $val2

Dòng code trên sẽ trả về true vì đối tượng nil là một đối tượng tĩnh và nó chỉ có 1.


nil
nil
true

Kiểu chuỗi – String

Một string là một dãy các kí tự liên tiếp nhau. Trong Ruby chúng là các đối tượng thuộc lớp String. Các chuỗi string được bọc trong cặp dấu nháy đơn hoặc nháy kép.

String là một kiểu dữ liệu quan trọng, trong phần sau chúng ta sẽ tìm hiểu kĩ hơn về kiểu dữ liệu này.

p "Hello"
p 'Ruby'

p "Ruby".size
p "Ruby".upcase

p 2.to_s

Trong đoạn code trên chúng ta in một số string và các tính chất của chúng ra màn hình.

p "Hello"
p 'Ruby'

String có thể được bọc trong cặp dấu nháy đơn hoặc nháy kép.

p "Ruby".size
p "Ruby".upcase

Ở 2 dòng trên chúng ta gọi đến phương thức size và phương thức upcase, phương thức size trả về độ lớn của string còn phương thức upcase chuyển một string thành dạng chữ in hoa.

p 2.to_s

Trong dòng trên phương thức to_s chuyển một số thành một string.

"Hello"
"Ruby"
4
"RUBY"
"2"

Mảng và bảng băm

Mảng và bảng băm lưu dữ liệu theo dạng tập hợp, tức là chúng không lưu từng giá trị cụ thể mà lưu một nhóm các phần tử có giá trị khác nhau.

Mảng lưu các phần tử theo một thứ tự trong khi bảng băm lưu phần tử theo các cặp khóa-giá trị. Chúng ta sẽ tìm hiểu thêm về 2 kiểu dữ liệu này trong các bài sau.

nums = [1, 2, 3, 4]

puts "There are #{nums.size} items in the array"

nums.each do |num|
    puts num
end


domains = { :de => "Germany", :sk => "Slovakia",
            :us => "United States", :no => "Norway" }

puts domains.keys
puts domains.values

Đoạn code trên ví dụ về mảng và bảng băm trong Ruby.

nums = [1, 2, 3, 4]

puts "There are #{nums.size} items in the array"

nums.each do |num|
    puts num
end

Trong đoạn code trên, dòng đầu tiên tạo một mảng có 4 phần tử. Dòng thứ hai sẽ in số lượng phần tử trong mảng ra màn hình. Các dòng sau duyệt qua mảng để lấy từng phần tử của mảng và in ra màn hình.

domains = { :de => "Germany", :sk => "Slovakia",
            :us => "United States", :no => "Norway" }

puts domains.keys
puts domains.values

Ở đây chúng ta tạo một đối tượng bảng băm và in danh sách khóa và giá trị ra màn hình.

There are 4 items in the array
1
2
3
4
de
sk
us
no
Germany
Slovakia
United States
Norway

Chuyển đổi kiểu dữ liệu

Ruby có các phương thức hỗ trợ chuyển đổi kiểu dữ liệu như to_i, to_s hay to_f. Ngoài trong module Kernel còn có các phương thức như Integer, String hoặc Float cũng làm công việc tương tự.

p Array(1..6)
p Complex 6
p Float 12
p Integer "34"
p Rational 6
p String 22

Đoạn code trên chuyển đổi kiểu dữ liệu bằng các phương thức của module Kernel.

[1, 2, 3, 4, 5, 6]
(6+0i)
12.0
34
(6/1)
"22"

Đoạn code dưới đây chuyển đổi đối tượng sang kiểu số nguyên và số chấm động.

p "12".to_i
p 12.5.to_i
p nil.to_i

p 12.to_f
p "11".to_f
p nil.to_f

Một số đối tượng trong Ruby chứa phương thức to_ito_f giúp chuyển đổi sang kiểu số nguyên và số chấm động tương ứng.

12
12
0
12.0
11.0
0.0

Chúng ta có thể chuyển một string sang khá nhiều kiểu dữ liệu khác nhau:

p "12".to_i
p "13".to_f
p "12".to_r
p "13".to_c

p "Jane".to_sym

v = "Ruby Python Tcl PHP Perl".split
p v.class

Trong đoạn code trên chúng ta chuyển string sang số nguyên, số thực, số hữu tỉ, số phức, mảng và thậm chí là thành một symbol.

v = "Ruby Python Tcl PHP Perl".split
p v.class

Phương thức split sẽ tách từng kí tự trong string và lưu trong một mảng.

12
13.0
(12/1)
(13+0i)
:Jane
Array

Ruby – Kiểu dữ liệu – Phần 1

Trong phần này chúng ta sẽ tìm hiểu về các kiểu dữ liệu.

Tất cả các chương trình máy tính trên thế giới đều sử dụng dữ liệu, từ trình soạn thảo văn bản, máy tính, game… dữ liệu cũng được chia làm nhiều loại khác nhau chẳng hạn như số, kí tự, hình ảnh, âm thanh… Kiểu dữ liệu là một tập các giá trị và các thao tác có thể có trên các giá trị đó.

Tất cả các kiểu dữ liệu trong Ruby đều là lớp. Ruby hỗ trợ một số kiểu dữ liệu cơ bản sau đây:

  • Boolean – kiểu luận lý
  • Symbol
  • Number – số
  • String – chuỗi kí tự
  • Array – mảng
  • Hashe – bảng băm

Ví dụ:

h = { :name => "Jane", :age => 17 }

p true.class, false.class
p "Ruby".class
p 1.class
p 4.5.class
p 3_463_456_457.class
p :age.class
p [1, 2, 3].class
p h.class

Trong đoạn code trên chúng ta in tên lớp của từng kiểu dữ liệu khác nhau.

p true.class, false.class

Kiểu boolean có 2 giá trị là True hoặc False.

p "Ruby".class

Kiểu string, chúng ta đã làm việc với kiểu này trong các bài trước.

p 1.class
p 4.5.class
p 3_463_456_457.class

Kiểu số, gồm số nguyên và số thực.

p :age.class

Kiểu Symbol.

p [1, 2, 3].class
p h.class

Ở đây chúng ta có một đối tượng mảng và một đối tượng bảng băm.

TrueClass
FalseClass
String
Fixnum
Float
Bignum
Symbol
Array
Hash

Kiểu Boolean

Nói một cách “triết lý” thì trên thế giới này có một số thứ tồn tại theo 2 trạng thái/thái cực khác nhau… chẳng hạn như có Thiên đàng và Địa ngục, Nước và Lửa, Nam và Nữ… kiểu dữ liệu boolean cũng vậy, đối tượng thuộc kiểu này có một trong 2 trạng thái là True (đúng) hoặc False (sai). Đây là một kiểu dữ liệu quan trọng mà hầu hết ngôn ngữ lập trình nào cũng có.

Ví dụ:

bool = [true, false]

male = bool[rand(2)]


if male
    puts "We will use name John"
else 
    puts "We will use name Victoria"
end

Trong đoạn code trên chúng ta sử dụng phương thức rand() để lấy giá trị ngẫu nhiên từ 0→1.

bool = [true, false]

Chúng ta có một biến tên là bool, đây là một mảng có 2 giá trị là true hoặc false. Một mảng được tạo bằng cách sử dụng cặp dấu [].

male = bool[rand(2)]

Khi phương thức rand() trả về 0 hoặc 1, chúng ta dùng kết quả đó để lấy giá trị tương ứng trong mảng bool, tức là nếu 0 thì biến male có giá tri là true, nếu 1 thì malefalse.

if male
    puts "We will use name John"
else 
    puts "We will use name Victoria"
end

Tùy vào giá trị của male mà chúng ta in ra câu lệnh tương ứng với từ khóa if..else..end.

We will use name Victoria

Kiểu Symbol

Nếu bạn biết kiểu enum trong C++, Java… thì trong Ruby chúng được gọi là Symbol. Symbol được dùng để biểu diễn các đối tượng khác. Dùng symbol sẽ tiết kiệm được nhiều tài nguyên hơn so với dùng biến thông thường. Tất cả các biến symbol đều là một đối tượng từ lớp Symbol. Để định nghĩa một symbol thì chúng ta thêm dấu hai chấm ":" vào trước giá trị, ví dụ :name. Hầu hết các đối tượng trong Ruby đều có phương thức to_sym để chuyển một đối tượng thành một symbol.

Symbol không thể thay đổi được giá trị. Thường thì symbol được dùng để làm khóa trong bảng băm.

p :name
p :name.class
p :name.methods.size
p "Jane".methods.size

p :name.object_id
p :name.object_id
p "name".object_id
p "name".object_id

Trong đoạn code trên chúng ta thực hiện một số thao tác với symbol.

p :name
p :name.class

Chúng ta in symbol và tên của chúng ra màn hình.

p :name.methods.size
p "Jane".methods.size

Chúng ta so sánh kích thước dữ liệu của một symbol và một string. Rõ ràng là một string có kích thước lớn hơn một symbol.

p :name.object_id
p :name.object_id
p "name".object_id
p "name".object_id

Symbol giống nhau thì có id giống nhau, string giống nhau thì lại có id khác nhau.

:name
Symbol
79
162
10328
10328
77344750
77344730

Ngoài ra Symbol cũng thường được dùng để dánh dấu, chẳng hạn như thay vì khai báo biến number = 1 rồi thực hiện if number == 1... thì ở đây chúng ta dùng symbol sẽ tiết kiệm được bộ nhớ cũng như dễ code hơn.

light = :on

if light == :on
    puts "The light is on"
else
    puts "The light is off"
end

light = :off

if light == :on
    puts "The light is on"
else
    puts "The light is off"
end

Trong đoạn code trên chúng ta in các đoạn text khác nhau tùy theo giá trị của biến light.

Symbol còn được dùng để làm khóa trong bảng băm.

domains = {:sk => "Slovakia", :no => "Norway", :hu => "Hungary"}

puts domains[:sk]
puts domains[:no]
puts domains[:hu]

Trong đoạn code trên chúng ta có một đối tượng bảng băm là domains, đối tượng này dùng khóa là các Symbol.

puts domains[:sk]
puts domains[:no]
puts domains[:hu]

Chúng ta lấy giá trị của từng phần tử trong bảng băm bằng khóa.

Slovakia
Norway
Hungary

Kiểu số nguyên – Integer

Trong lập trình thì thì integer là một kiểu dữ liệu cơ bản, mặc dù trong toán thì số nguyên là  tập hợp Z, một tập hợp vô hạn, nhưng trong máy tính thì không có cái gì vô hạn cả, chúng ta có thể có tập hợp các số nguyên nhưng tập hợp này chỉ trong một phạm vi nhất định.

Trong Ruby thì số nguyên là các đối tượng thuộc lớp Fixnum hoặc Bignum, 2 lớp này có độ lớn khác nhau, độ lớn là khoảng giá trị của một đối tượng, ví dụ như trong C++, Java thì một số nguyên thường có độ lớn từ -2^32 đến 2^32, trong Ruby thì độ lớn của Fixnum tùy thuộc vào hệ điều hành, nếu một số nguyên có giá trị lớn hơn ngưỡng cho phép thì sẽ tự động được chuyển thành kiểu Bignum nên chúng ta cũng không cần phải quan tâm lắm đến vấn đề này.

p -2
p 121
p 123265
p -34253464356
p 34867367893463476

p 1.class
p 23453246.class
p 234532423563456346.class
p 2345324235632363463456456346.class

p 5 / 2
p 5.div 2

Đoạn code trên thực hiện một số thao tác với số nguyên.

p -2
p 121
p 123265
p -34253464356
p 34867367893463476

Trên đây là các con số cả âm lẫn không âm với các độ lớn khác nhau.

p 1.class
p 23453246.class
p 234532423563456346.class
p 2345324235632363463456456346.class

Chúng ta in ra tên lớp của các số nguyên. Hai số đầu tiên có kiểu Fixnum trong khi 2 số sau có kiểu Bignum.

p 5 / 2
p 5.div 2

Chúng ta thực hiện phép chia 2 số nguyên bằng toán tử / và phương thức div. Phép chia một số nguyên cũng sẽ trả về một số nguyên.

-2
121
123265
-34253464356
34867367893463476
Fixnum
Fixnum
Bignum
Bignum
2
2

Số nguyên còn có thể được biểu diễn trong nhiều hệ cơ số khác nhau như hệ thập phân (10), hệ thập lục phân (16), hệ bát phân (8), và hệ nhị phân (2). Hệ thập phân là hệ cơ số mà chúng ta thường dùng nhất, hệ thập lục phân được biểu diễn với kí tự 0x ở đầu, hệ bát phân dùng kí tự 0 và hệ nhị phân dùng kí tự 0b.

puts 122
puts 0x7a
puts 0172
puts 0b1111010

Đoạn code trên in số 122 ở bốn hệ cơ số khác nhau.

122
122
122
122

Bignum hay “số nguyên lớn” là các con số có số lượng chữ số rất lớn, thường là vài trăm chữ số, đọc số nguyên lớn cũng rất khó chịu, chẳng hạn như số 936758346345906394… do đó Ruby cho phép số nguyên lớn có thể có dấu gạch dưới “_” để chúng ta dễ đọc hơn, khi dịch thì trình thông dịch Ruby sẽ bỏ qua các dấu gạch dưới đó.

p 23482345629
p 23_482_345_629

p 23482345629 == 23_482_345_629

Đoạn code trên sử dụng dấu gạch dưới cho số nguyên lớn.

23482345629
23482345629
true

Số chấm động

Số chấm động tức là số thực, số thực thường được dùng để đo các giá trị liên tục như trọng lượng, tốc độ, chiều dài… Trong Ruby thì số thực được biểu diễn bởi lớp Float hoặc BigDecimal.

p 15.4
p 0.3455
p -343.4563

p 12.5.class
p -12.5.class
p (5.0 / 2).class

p 5.fdiv 2
p 12.to_f

Đoạn code trên thực hiện một số thao tác với số chấm động.

p 15.4
p 0.3455
p -343.4563

Chúng ta in 3 số chấm động, số chấm động có phần thập phân nằm phía sau dấu chấm.

p 12.5.class
p -12.5.class
p (5.0 / 2).class

Chúng ta in tên lớp biểu diễn số chấm động. Ngoài ra nếu chúng ta thực hiện tính toán một số nguyên với một số chấm động thì kết quả sẽ cho ra một số chấm động.

p 5.fdiv 2
p 12.to_f

Chúng ta tạo các số chấm động bằng phương thức fdiv và phương thức to_f. Phương thức fdiv chẳng qua là thực hiện phép chia nhưng trả về số chấm động mặc dù phép chia đó thực hiện trên 2 số nguyên, còn phương thức to_f sẽ chuyển đổi một số bất kì thành số thực.

15.4
0.3455
-343.4563
Float
Float
Float
2.5
12.0

Mặc định thì phần thập phân chỉ hiển thị tối đa 16 chữ số nhưng chúng ta có thể định dạng kiểu hiển thị theo ý chúng ta với phương thức sprintf hoặc printf.

p 1/3.0
p 1.fdiv 2

puts sprintf "%.4f" % (1/3.0)
puts sprintf "%.7f" % (5/3.0)

Đoạn code trên sẽ định dạng hiển thị số chấm động.

p 1/3.0
p 13.fdiv 4
p 1.fdiv 2

Phương thức p sẽ in các số chấm động một cách mặc định.

puts sprintf "%.4f" % (1/3.0)
puts sprintf "%.7f" % (5/3.0)

Chúng ta định dạng lại số chấm động bằng phương thức sprintf, phương thức này nhận vào một chuỗi định dạng, chuỗi này có dạng %... ở đây %.4f tức là hiển thị 4 chữ số phía sau dấu chấm, tương tự %.7f là hiển thị 7 chữ số, kí tự f cho Ruby biết kiểu định dạng này được sử dụng với số chấm động.

0.3333333333333333
3.25
0.5
0.3333
1.6666667

 

Ruby – Đối tượng

Trong phần này chúng ta sẽ tìm hiểu sơ qua về đối tượng trong Ruby, các bài sau sẽ trình bày chi tiết hơn.

Ruby là một ngôn ngữ lập trình hướng đối tượng. Tất cả mọi thứ trong Ruby đều là đối tượng. Các đối tượng trong Ruby tồn tại trong suốt quá trình biên dịch code. Có 2 loại đối tượng là đối tượng có sẵn và đối tượng do chúng ta định nghĩa. Đối tượng có sẵn là các đối tượng đã được định nghĩa sẵn trong Ruby hoặc các thư viện do người khác viết bằng Ruby và chúng ta có thể sử dụng. Còn loại kia là đối tượng do chúng ta định nghĩa để sử dụng với công việc đặc thù của riêng chúng ta khi các đối tượng có sẵn không đáp ứng được.

Muốn sử dụng đối tượng thì chúng ta phải khởi tạo trước. Một đối tượng chứa thuộc tính và phương thức bên trong nó. Thuộc tính là dữ liệu của đối tượng, đây là thành phần tĩnh, phương thức là phần động. Đối tượng có thể được chỉnh sửa hoặc giao tiếp với các đối tượng khác thông qua phương thức.

puts "Ruby language"

Nếu bạn đã từng lập trình C++, Java thì bạn cũng đã biết tới những từ khóa hoặc hàm dùng để in một chuỗi ra màn hìnhchẳng hạn như hàm printf() trong C, std::cout trong C++, System.out.println() trong Java… các hàm này có chức năng là in một chuỗi ra màn hình, từ khóa puts trong Ruby ở trên cũng có chức năng tương tự. Các từ khóa/hàm này nhận vào một giá trị là một chuỗi, trong ví dụ trên thì “Ruby language” là một giá trị,

Nhưng trong Ruby thì có hơi khác, bản thân chuỗi “Ruby language” cũng là một đối tượng. Do đó chúng ta cũng có thể gọi phương thức từ chuỗi này.

Ngoài ra phương thức tồn tại trong các đối tượng chứ không tự tồn tại một mình ở đâu đó được, puts cũng là một phương thức của một đối tượng đó là module Kernel.

Ví dụ 1:

Kernel.puts "Ruby language"
Kernel.puts "Ruby language".size

Trong dòng code đầu tiên chúng ta gọi phương thức puts và gọi cả tên module Kernel ra luôn. Trong C# hay Java cũng tương tự là Console.writelnSystem.out.println(). Tất nhiên là chúng ta cũng không cần gọi Kernel ra như vậy trong Ruby nhưng ví dụ này chỉ để khẳng định là phương thức thì luôn đi kèm với một đối tượng nào đó.

Trong dòng tiếp theo chúng ta in ra kích thước của chuỗi “Ruby language”, điều này chứng tỏ chuỗi “Ruby language” cũng chính là một đối tượng chứ không đơn thuần chỉ là một giá trị như trong các ngôn ngữ khác. Phương thức size sẽ trả về số lượng kí tự trong một chuỗi.

Ruby language
13

Ví dụ 2:


puts 6.object_id

puts 6.even?
puts 6.zero?

puts 6.class

Chúng ta có số 6 và cũng có thể gọi một số phương thức của số 6 này.

puts 6.object_id

Mỗi đối tượng đều được gán một id và chúng ta có thể lấy giá trị đó ra bằng cách gọi phương thức object_id. Mỗi lời gọi phương thức đều được đặt sau dấu chấm sau tên đối tượng.

puts 6.even?
puts 6.zero?

Phương thức even? trả về True nếu số đó là số chẵn và ngược lại. Phương thức zero? trả về True nếu số đó là 0. Ở đây có một điểm lưu ý là nếu phương thức trả về True hoặc False thì phải kèm thêm dấu ? sau tên phương thức.

puts 6.class

Phương thức class cho biết tên lớp của đối tượng, ở đây số 6 là một đối tượng thuộc lớp Fixnum.

13
true
false
Fixnum

Tạo đối tượng

Một đối tượng phải được tạo ra thì mới có thể sử dụng được. Đối tượng trong Ruby có thể được tạo ra một cách rõ ràng hoặc tạo ngầm. Ví dụ về đối tượng ngầm là mấy cái đối tượng được tạo ra từ giá trị như số 6 hay chuỗi “Ruby language” ở trên. Còn để tạo đối tượng một cách rõ ràng thì chúng ta dùng từ khóa new. Đối tượng thuộc lớp do chúng ta tự định nhĩa cũng dùng từ khóa new.


class Being
end
    
puts 67
puts "Hello world"

s = String.new "Hello world"
puts s

# n1 = Fixnum.new 67
# puts n1

b = Being.new
puts b

Trong đoạn code trên chúng ta tạo một số đối tượng bằng các cách khác nhau.

class Being
end

Chúng ta định nghĩa lớp Being. Để định nghĩa một lớp thì chúng ta dùng từ khóa class.

puts 67
puts "Hello world"

Hai dòng code trên tạo đối tượng ngầm là 67 và chuỗi “Hello world”.

s = String.new "Hello world"
puts s

Tương tự, chúng ta có thể tạo đối tượng string một cách rõ ràng luôn với từ khóa new.

# n1 = Fixnum.new 67
# puts n1

Không phải tất cả các lớp có sẵn trong Ruby đều có thể được tạo ra bằng từ khóa new, Fixnum là một ví dụ, các đối tượng Fixnum chỉ có thể được tạo ngầm.

b = Being.new
puts b

Đoạn code trên tạo đối tượng b từ lớp Being do chúng ta định nghĩa.

67
Hello world
Hello world
#<Being:0x8492o3a>

Đối tượng giá trị

Như đã nói ở trên là chúng ta có thể tạo đối tượng một cách ngầm định từ các giá trị. Ở đây chúng ta sẽ làm việc với một số phương thức của loại đối tượng này.

4.times { puts "Ruby" }

puts "Ruby".size
puts "Ruby".downcase

puts [1, 2, 3].include? 3
puts [1, 2, 3].empty?

puts :name.class
puts :name.frozen?

puts (1..6).class
puts (1..6).include? 4

Trong đoạn code trên chúng ta có các đối tượng Fixnum, String, Array, kiểu Symbol và kiểu Range. Chúng ta sẽ tìm hiểu kỹ hơn về các kiểu đối tượng này trong bài sau.

4.times { puts "Ruby" }

Phương thức times của đối tượng Fixnum thực hiện phép nhân lên giá trị phía sau nó, ở đây là thực hiện puts "Ruby" 4 lần.

puts "Ruby".size
puts "Ruby".downcase

Phương thức size lấy về số kí tự trong chuỗi, phương thức downcase thực hiện chuyển chuỗi về kiểu chữ in thường.

puts [1, 2, 3].include? 3
puts [1, 2, 3].empty?

Trong 2 dòng code trên chúng ta sử dụng đối tượng kiểu mảng – Array, phương thức include? cho biết một giá trị nào đó có thuộc mảng hay không. Phương thức empty? cho biết mảng này rỗng hay không.

puts :name.class
puts :name.frozen?

Ở 2 dòng trên chúng ta thao tác với kiểu Symbol, symbol có tên bắt đầu bằng dấu 2 chấm ":" chúng ta sẽ tìm hiểu thêm trong bài sau.

puts (1..6).class
puts (1..6).include? 4

Cuối cùng là 2 đối tượng kiểu Range, về bản chất thì kiểu này cũng tương tự như kiểu mảng vậy. Phương thức class trả về tên của kiểu dữ liệu này, còn phương thức include? cho biết giá trị nào đó có nằm trong danh sách hay không.

Ruby
Ruby
Ruby
Ruby
4
ruby
true
false
Symbol
false
Range
true

Thừa kế

Một trong những khái niệm trong lập trình hướng đối tượng là thừa kế và Ruby cũng thế. Thừa kế bao gồm các đối tượng cha và đối tượng con, đối tượng cha sẽ chứa những thuộc tính và phương thức mà đối tượng con có thể thừa kế lại. Tất cả các đối tượng trong Ruby đều thừa kế từ một đối tượng gốc có tên là Object. Tất cả các đối tượng trong Ruby đều có các phương thức mà đối tượng Object có, nhưng có thể định nghĩa lại.

puts 4.is_a? Object
puts "Ruby".is_a? Object
puts [2, 3].is_a? Object
puts :name.is_a? Object
puts (1..2).is_a? Object

Đoạn code trên cho chúng ta thấy tất cả các đối tượng đều thừa kế từ đối tượng Object.

puts 4.is_a? Object

Trong dòng code trên phương thức is_a? cho biết đối tượng 4 có được kế thừa từ đối tượng Object hay không.

true
true
true
true
true

Trong ví dụ dưới đây, chúng ta sẽ định nghĩa các lớp có kế thừa nhau.

class Being

    def to_s
        "This is Being"
    end
    
    def get_id
        9
    end
end

class Living < Being
    
    def to_s
        "This is Living"
    end
end

l = Living.new

puts l
puts l.get_id
puts l.is_a? Being

Chúng ta định nghĩa 2 lớp BeingLiving, lớp Living kế thừa từ lớp Being, do đó lớp Being là lớp cha, lớp Living là lớp con.

class Being

    def to_s
        "This is Being"
    end
    
    def get_id
        9
    end
end

Để định nghĩa một lớp thì chúng ta sử dụng cặp từ khóa class...end, sau từ khóa class là tên lớp mà chúng ta muốn đặt. Bên trong lớp chúng ta có thể định nghĩa các phương thức bằng cách dùng cặp từ khóa def…end, sau từ khóa def cũng từ tên phương thức mà chúng ta muốn đặt. Trong đoạn code trên chúng ta có 2 phương thức là to_sget_id.

Đối tượng nào cũng có phương thức to_s cả vì mặc định đối tượng Object cũng có phương thức này. Khi chúng ta dùng phương thức puts để in ra một giá trị nào đó thì phương thức này sẽ tự động gọi đến phương thức to_s có trong các đối tượng. Trong ví dụ này chúng ta định nghĩa lại phương thức to_s, nếu không thì puts sẽ sử dụng phương thức to_s mặc định của đối tượng Object.

class Living < Being
    
    def to_s
        "This is Living"
    end
end

Chúng ta định nghĩa lớp Living, lớp này kế thừa từ lớp Being, để một lớp được kế thừa từ lớp khác thì chúng ta thêm dấu < cùng với tên của lớp cha vào sau tên lớp con. Ở đây lớp Living cũng định nghĩa lại phương thức to_s của riêng nó.

l = Living.new

Chúng ta tạo đối tượng Living bằng cách dùng từ khóa new.

puts l

Phương thức puts sẽ gọi đến phương thức to_s trong lớp Living.

puts l.get_id

Lớp Living không định nghĩa lại phương thức get_id nên Ruby sẽ tìm dần dần các lớp cha xem lớp nào có thì gọi phương thức get_id từ lớp đó.

puts l.is_a? Being

Dòng code trên sẽ trả về True vì lớp Living kế thừa từ lớp Being nên một đối tượng Living cũng là một đối tượng Being.

This is Living
9
true

Đối tượng main

Trong các ngôn ngữ như C++, Java… thì chương trình bắt đầu chạy từ một hàm đặc biệt tên là hàm main(), khi chạy chương trình hàm này sẽ được gọi từ hệ điều hành.

Trong Ruby cũng thế, mỗi đoạn code trong file .rb đều nằm trong một đối tượng có tên là main mặc dù ở đây chúng ta không khai báo ra, đối tượng này cũng kế thừa từ đối tượng Object. Chính vì main cũng là một đối tượng nên các biến được khai báo trong main cũng là thuộc tính của main.

n1 = 3
n2 = 5

puts local_variables

Kernel.puts self
puts self.class

Đoạn code trên ví dụ về đối tượng main trong Ruby.

n1 = 3
n2 = 5

Chúng ta có 2 đối tượng số nguyên. Cả hai đối tượng này đều thuộc về đối tượng main.

puts local_variables

Biến local_variables thực chất là một phương thức của module Kernel, lưu trữ danh sách các biến cục bộ hiện có.

Kernel.puts self

Biến self là biến tham chiếu đến đối tượng hiện tại, nói cho dễ hiểu thì self chính là main, nếu bạn biết con trỏ this trong C++, Java… thì self cũng giống như con trỏ this vậy.

puts self.class

Biến class cho biết tên lớp của đối tượng hiện tại, hiện tại chúng ta dùng đối tượng self (hoặc main) mà đây là đối tượng thuộc lớp Object nên dòng trên sẽ in ra chuỗi Object.

n1
n2
main
Object

 

Dạy kèm/Hỗ trợ bài tập/Đồ án cho sinh viên

Chào các bạn, mình tên là Long, admin của blog Phở code. Mình đang học thạc sĩ Computer Science ở Đức.

Mình thành lập blog Phở Code từ năm 2016, kể từ đó đến nay đã có hơn 1 triệu lượt truy cập vào blog. Điều này khiến mình rất vui, tuy nhiên trong quá trình đó mình cũng nhận được rất nhiều câu hỏi từ các bạn, mà đa phần là các câu hỏi chắp vá, và cũng thường không biết phải hỏi như thế nào cho đúng… lí do là vì các bạn bị hổng kiến thức nền. Một đối tượng nữa là các bạn đã tốt nghiệp và đi làm được một thời gian dài nhưng muốn chuyển sang học lập trình và không muốn học đại học.

Do đó mình nhận dạy kèm như sau:

  • Môn lập trình từ A-Z cho các bạn bị mất kiến thức cơ bản, mất tư duy lập trình, hoặc người đi làm muốn học lập trình trên một trong các ngôn ngữ sau: C, C++, Java, Javascript, PHP, Python.
  • Môn cấu trúc dữ liệu & giải thuật.
  • Hướng dẫn làm bài tập lớn/Đồ án/Project liên quan tới lập trình.
  • Ngoài ra mình nhận dạy môn tiếng Anh chuyên ngành CNTT, sẽ bao gồm phần tiếng Anh cho CNTT và kĩ năng tự tìm kiếm tài liệu.

Mình cũng từng gặp khó khăn khi lần đầu tiên tiếp cận môn lập trình, nên mình hiểu khó khăn mà các bạn đang gặp phải, mình cam đoan tận tụy giúp đỡ các bạn vượt qua rào cản và có thêm nhiều hứng thú với lĩnh vực này.

Hình thức học

Hình thức học có thể là online (qua Zoom hoặc Skype), hoặc trực tiếp tại nhà nếu bạn ở Nha Trang (nơi mình sống hiện tại).

Học phí

Tùy vào các bạn cần phụ đạo/giúp đỡ như thế nào và trình độ hiện tại của các bạn mà học phí sẽ khác nhau. Hãy liên lạc với mình để bàn bạc thêm.

Liên hệ học

Các bạn có nhu cầu có thể liên lạc với mình theo các cách sau:

Nếu những môn bạn cần giúp đỡ không có trong danh sách trên thì cũng hãy liên hệ với mình, nếu mình thấy mình có thể giúp được thì mình sẽ hỗ trợ.

Django – Sessions

Đôi khi chúng ta muốn lưu lại một số thông tin trong quá trình duyệt web của user để sử dụng lại sau này, session cho phép chúng ta lưu trữ lại một số thông tin trên từng user, session có nhiều loại, có loại lưu dữ liệu trên server, có loại lưu trên client… Trong phần này chúng ta sẽ tìm hiểu về hệ thống session của Django.

Kích hoạt session

Chúng ta kích hoạt session bằng cách khai báo django.contrib.middle.SessionMiddleware trong biến MIDDLEWARE_CLASSES trong file settings.py:

MIDDLEWARE_CLASSES = [
    #...,
    'django.contrib.sessions.middleware.SessionMiddleware',
    #...
]

Mặc định thì session đã được kích hoạt sẵn khi tạo project nên chúng ta cũng không cần phải chỉnh sửa gì trong này.

Cấu hình session

Dữ liệu trong session có thể được lưu trong cơ sở dữ liệu, file hoặc trong cache, mặc định thì Django lưu trong CSDL.

Lưu session trong cơ sở dữ liệu

Để cấu hình session lưu trong cơ sở dữ liệu thì chúng ta khai báo django.contrib.sessions trong biến INSTALLED_APPS:

INSTALLED_APPS = [
    #...
    'django.contrib.sessions',
    #...
]

Sau khi thiết lập thì chúng ta phải chạy lệnh manage.py migrate để Django tạo bảng tương ứng trong CSDL.

Lưu session trong cache

Bạn chỉ nên dùng loại session này nếu bạn thiết lập kiểu cache của server là memcachedĐể lưu session trong cache thì chúng ta thiết lập biến SESSION_ENGINE trong file settings.py là:

  • django.contrib.sessions.backends.cache: session lưu trong loại này không được đảm bảo vì nếu bộ nhớ cache đầy thì dữ liệu session sẽ bị xóa, nhưng loại này truy xuất dữ liệu cũng như ghi dữ liệu rất nhanh.
  • django.contrib.sessions.backends.cached_db: loại này thì vừa lưu dữ liệu session trong cache vừa lưu vào CSDL luôn, nếu session trong cache bị xóa thì Django sẽ tìm session trong CSDL nên dữ liệu được đảm bảo hơn loại trên nhưng cũng vì thế và tốc độ chậm hơn.

Trong thực tế thì chúng ta sẽ dùng loại thứ 2 vì dữ liệu session thường cũng không lớn nên việc đọc ghi sẽ không tốn thời gian mấy. Cũng chính vì loại thứ 2 lưu session trong cơ sở dữ liệu nên chúng ta cũng phải thiết lập luôn cả phần Lưu session trong cơ sở dữ liệu ở trên.

Lưu session trong file

Để lưu session trong file thì chúng ta khai báo SESSION_ENGINEdjango.contrib.sessions.backends.fileSESSION_FILE_PATH là đường dẫn đến tên file dùng để lưu session, đường dẫn phải là đường dẫn tuyệt đối và server phải có quyền đọc/ghi file trên đĩa cứng.

Lưu session trong cookie

Chúng ta khai báo SESSION_ENGINEdjango.contrib.sessions.backends.signed_cookies. Bạn để ý là trong file settings.py có một biến tên là SECRET_KEY có giá trị là một chuỗi được Django tạo ra ngẫu nhiên, biến này sẽ được dùng để mã hóa dữ liệu trong sessions, chúng ta sẽ tìm hiểu về biến này sau.

Truy xuất session trong view

Tham số request trong hàm view sẽ chứa một thuộc tính có tên là session khi SessionMiddleware được kích thoạt, đây là một đối tượng dictionary. Chúng ta có thể đọc/ghi thuộc tính này ở bất kỳ đâu trong hàm view.

Dưới đây là một số thao tác với session:

Lấy giá trị theo khóa:

>>> color = request.session['color'] 
>>> color1 = request.session.get('color', 'red')

Tham số 'red' là tham số trả về mặc định, tức là nếu không tìm thấy khóa 'color' trong session thì trả về giá trị 'red'.

Thiết lập khóa:

>>> request.session['color'] = 'blue'

Xóa khóa ra khỏi session, nếu không tìm thấy khóa thì báo lỗi KeyError:

>>> del request.session['color']

Kiểm tra xem khóa có tồn tại trong session:

>>> 'color' in request.session

Xóa toàn bộ dữ liệu session:

>>> request.session.flush()

Thiết lập thời gian tồn tại cho session, tham số nhận vào là số giây, nếu để 0 thì tồn tại cho đến khi user tắt trình duyệt.

>>> request.session.set_expiry(300)

Lấy thời gian còn lại của sessiontheo giây.

>>> request.session.get_expiry_age()

Ví dụ

Chúng ta sẽ xây dựng một hệ thống login (đăng nhập) có hỗ trợ session đơn giản. Mặc định project của mình có tên cũng như thư mục là mysite. 

Chúng ta tạo app có tên là login (nhớ khai báo trong biến INSTALLED_APP):

C:\Project\mysite>python manage.py startapp login

Bên trong app chúng ta tạo lớp form với tên LoginForm:

from django import forms

class LoginForm(forms.Form):
    username = forms.CharField(max_length = 100)
    password = forms.CharField(widget = forms.PasswordInput())

Tiếp theo chúng ta tạo 2 trang template, một trang dùng để hiển thị form đăng nhập, một trang template để hiển thị thông báo đăng nhập thành công.


<form action="{% url 'login.views.loginView' %}" method="POST">
    {% csrf_token %}
    <table>
        <tr>
            <td>Username:</td>
            <td><input type="text" name="username" /></td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><input type="password" name="password" /></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="Login"/></td>
        </tr>
    </table>
</form>

File login.html dùng để hiển thị form đăng nhập. Tại đây thuộc tính action trong thẻ form trỏ đến hàm loginView mà chúng ta sẽ viết ở dưới.

Hello, <strong>{{username}}</strong>

File loggedin.html hiển thị thông báo đăng nhập thành công.

Tiếp theo chúng ta viết các hàm view.

from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
from login.forms import LoginForm

def loginView(request):
    username = "Wrong username or password"
 
    if request.method == "POST":
        MyLoginForm = LoginForm(request.POST) 
        if MyLoginForm.is_valid(): 
            if MyLoginForm.cleaned_data['username'] == 'admin': 
                if MyLoginForm.cleaned_data['password'] == '123':
                    username = MyLoginForm.cleaned_data['username'] 
                    request.session['username'] = username
                    request.session.set_expiry(15);
    else:
        MyLoginForm = LoginForm()
 
    return render(request, 'loggedin.html', {'username':username})
 
def formView(request):
    if request.session.has_key('username'):
        username = request.session['username']
        return render(request, 'loggedin.html', {'username':username})
    else:
        return render(request, 'login.html', {})
 
def logoutView(request):
    try:
        del request.session['username']
    except:
        pass
    return HttpResponse("Good bye!")

Chúng ta viết 3 hàm view là loginView(), formView()logoutView().

if MyLoginForm.cleaned_data['username'] == 'admin': 
    if MyLoginForm.cleaned_data['password'] == '123':

Hàm loginView() sẽ kiểm tra dữ liệu được gửi lên. Ở đây chúng ta chỉ kiểm tra đơn giản với usernameadminpassword123. 

username = MyLoginForm.cleaned_data['username'] 
request.session['username'] = username
request.session.set_expiry(15);

Nếu dữ liệu phù hợp thì chúng ta thiết lập khóa username và thời gian hiệu lực là 15 giây trong thuộc tính session.

if request.session.has_key('username'):
    username = request.session['username']
    return render(request, 'loggedin.html', {'username':username})
else:
    return render(request, 'login.html', {})

Hàm formView() sẽ kiểm tra xem session có chứa dữ liệu hay không bằng phương thức has_key(), nếu chưa có thì tạo form đăng nhập với template login.html. Còn nếu có rồi thì hiển thị câu chào mừng trong template loggedin.html.

try:
    del request.session['username']

Hàm logoutView() có nhiệm vụ xóa khóa username trong session nếu có.

Cuối cùng là tạo URL:

from django.conf.urls import url, include, patterns

urlpatterns = patterns('login.views',
    url(r'^login/', 'loginView'),
    url(r'^greeting/', 'formView'),
    url(r'^logout/', 'logoutView')
)

Khác với các bài trước là chúng ta tạo URL cho riêng từng app, ở đây chúng ta cho trỏ URL thẳng đến các hàm view trong app luôn. Lớp patterns nhận vào tham số đầu tiên là đường dẫn đến module chứa các hàm view, các tham số tiếp theo là các đối tượng url.

Bây giờ chúng ta có thể chạy server và trỏ đến URL localhost:8000/greeting để đăng nhập.

Capture

Đăng nhập với usernameadminpassword123 để được chuyển đến trang chào mừng.

Capture1

Nếu chưa hết 15 giây mà chúng ta lại trỏ đến localhost:8000/greeting thì Django sẽ hiện ra trang “Hello,…” luôn chứ không hiện ra form đăng nhập nữa.

Ngoài ra chúng ta có thể trỏ đến localhost:8000/logout để xóa session là có thể đăng nhập lại.

Capture2

Ruby – Biến

Biến là nơi lưu trữ dữ liệu. Mỗi biến có một tên riêng, tên biến được đặt theo một số quy luật riêng. Mỗi biến có một kiểu dữ liệu riêng. Ruby có rất nhiều kiểu dữ liệu có sẵn. Kiểu dữ liệu trong Ruby là kiểu dữ liệu động, tức là khi chúng ta khai báo biến và gán giá trị thì Ruby sẽ tự động gán kiểu dữ liệu cho biến dựa trên giá trị đó chứ chúng ta không cần phải khai báo trước kiểu dữ liệu như trong các ngôn ngữ như C++, Java…


i = 5
puts i
i = 7
puts i

Giá trị của biến có thể thay đổi được. Trong đoạn code trên chúng ta có biến i có giá trị ban đầu là 5, sau đó thay bằng 7.

Quy tắc đặt tên biến

Tên biến là có phân biệt HOA-thường. Tức là biến age và biến Age là 2 biến khác nhau.


i = 5
p i
I = 7
p I

Trong đoạn code trên chúng ta có biến i và biến I mang các giá trị khác nhau.

5
7

Biến được đặt tên bằng các kí tự chữ cái, chữ số và dấu gạch dưới “_”, nhưng không thể bắt đầu bằng chữ số và chữ cái in hoa.


name = "Jane"
placeOfBirth = "Bratislava"
placeOfBirth = "Kosice"
favorite_season = "autumn"

n1 = 2
n2 = 4
n3 = 7

p name, placeOfBirth, favorite_season
p n1, n2, n3

Trong đoạn code trên n1, n2n3 là các tên biến hợp lệ.

Khi đặt tên biến chúng ta nên đặt tên sao cho dễ nhớ và dễ sử dụng.

Sigil

Biến trong Ruby có thể bắt đầu bằng một số kí tự đặc biệt và được gọi là sigil, các kí tự sigil dùng để chỉ ra phạm vi hoạt động của biến.


tree_name = "pine"
$car_name = "Peugeot"
@sea_name = "Black sea"
@@species = "Cat"

p local_variables
p global_variables.include? :$car_name
p self.instance_variables
p Object.class_variables

Trong đoạn code trên chúng ta có 4 biến với 4 phạm vi hoạt động khác nhau tùy theo từng kí hiệu sigil.

tree_name = "pine"

Biến không có kí tự đặc biệt nào được gọi là biến cục bộ, tức là chỉ có hiệu lực ở phạm vi trong từng phương thức, lớp… nhất định.

$car_name = "Peugeot"

Kí tự $ cho biết biến đó là biến toàn cục, biến toàn cục có hiệu lực trong toàn bộ code Ruby.

@sea_name = "Black sea"

Kí tự @ cho biết biến đó là biến instance, biến này chỉ có hiệu lực trong một đối tượng.

@@species = "Cat"

Cuối cùng là kí tự @@ tức biến class, tất cả các đối tượng của một class đều có thể truy xuất biến này.

Chúng ta sẽ tìm hiểu thêm về các loại biến này ở dưới và trong các bài sau.

p local_variables

Biến local_variables là một mảng lưu trữ toàn bộ các biến cục bộ hiện có.

p global_variables.include? :$car_name

Tương tự, chúng ta có biến global_variables lưu toàn bộ các biến toàn cục, nhưng ở đây chúng ta không cho in ra toàn bộ vì số lượng biến toàn cục có sẵn rất nhiều, thay vào đó chúng ta dùng phương thức include? để kiểm tra xem biến $car_name của chúng ta có nằm trong danh sách đó hay không.

p self.instance_variables

Ở trên chúng ta tham chiếu đến biến instance_variables, biến này lưu trữ toàn bộ biến instance trong đối tượng hiện tại, ở đây là những biến instance được khai báo trong chương trình – tức là @sea_name.

Ngoài ra ở đây chúng ta còn dùng thêm biến self nữa, biến self tham chiếu đến đối tượng hiện tại đang được sử dụng, ở đây là chương trình chính, nếu bạn đã từng làm việc với con trỏ this trong C++, Java… thì self cũng giống như con trỏ this vậy. Thực ra ở đây bạn cũng không cần dùng đến self vì chúng ta đang gọi hàm ngay trong chương trình chính chứ không phải bên trong một phương thức hay lớp nào đó.

p Object.class_variables

Cuối cùng là biến class_variables,  biến này lưu trữ toàn bộ biến class có trong chương trình.

[:tree_name]
true
[:@sea_name]
[:@@species]

Biến cục bộ

Biến cục bộ là biến chỉ có hiệu lực trong một phạm vi nhất định trong toàn bộ code Ruby, cụ thể là biến nằm bên trong hàm, phương thức, class.

def method1
   x = 5
   p x    
end

method1 

p x

Trong đoạn code trên chúng ta có phương thức method1 có một biến là x, biến này là biến cục bộ, chúng ta chỉ có thể sử dụng biến này giữa cặp từ khóa def...end.

method1 

Chúng ta gọi phương thức bằng cách ghi rõ tên phương thức ra.

p x

Nếu chúng ta truy xuất biến ở ngoài phạm vi phương thức thì Ruby sẽ báo lỗi NameError vì Ruby không tìm thấy biến đó.

5
./locals.rb:11:in `<main>': undefined local variable 
or method `x' for main:Object (NameError)

Đoạn code dưới đây chỉnh sửa lại từ đoạn code trên một tí.


x = 5

def method1
    x = 10
    p x
end

method1

p x

Trong đoạn code trên chúng ta có 2 biến tên là x, một biến nằm trong phương thức method1 và một biến nằm ở ngoài, mặc dù chúng có cùng tên nhưng đây là 2 biến khác nhau hoàn toàn.

x = 5

Biến x đầu tiên được gán giá trị là 5, biến này có phạm vi trong toàn đoạn code nhưng không thể truy cập ở bên trong phương thức method1.

def method1
    x = 10
    p x
end

Bên trong phươn thức method1 lại có một biến x khác nữa được gán giá trị là 10. Biến này chỉ có hiệu lực bên trong cặp từ khóa def...end.

10
5

Phương thức có thể nhận vào các tham số, các tham số khi truyền vào phương thức sẽ có hiệu lực như một biến cục bộ bên trong phương thức đó:


def rectangle_area a, b
    puts local_variables
    return a * b
end

puts rectangle_area 5, 6

Ở trên chúng ta có phương thức rectangle_area nhận vào 2 tham số ab. Phương thức này tính và trả về diện tích hình chữ nhật.

puts rectangle_area 5, 6

Để truyền tham số vào phương thức thì chúng ta ghi giá trị hoặc biến vào sau lời gọi phương thức.

a
b
30

Một phương thức có thể được định nghĩa bên trong một phương thức khác, phương thức đó gọi là phương thức nội, phương thức nội cũng có biến cục bộ của riêng nó.

def method1
    
    def method2
                
        def method3
            m5, m6 = 3
            puts "Level 3"
            puts local_variables
        end            
        
        m3, m4 = 3
        puts "Level 2"
        puts local_variables
        method3    
    end        
    
    m1, m2 = 3
    puts "Level 1"
    puts local_variables
    method2
            
end

method1

Trong đoạn code trên chúng ta định nghĩa 3 phương thức là method1, method2method3, trong đó method2method3 là các phương thức nội. Phương thức method2 nằm bên trong method1method3 lại nằm bên trong method2. Các biến nằm ở phương thức nào thì chỉ có hiệu lực ở phạm vi phương thức đó.

Level 1
m1
m2
Level 2
m3
m4
Level 3
m5
m6

Biến toàn cục

Biến toàn cục khác biến cục bộ ở chỗ là có hiệu lực trên toàn code. Biến toàn cục có tên bắt đầu bằng ký tự $.


$gb = 6


module ModuleM        
    puts "Inside module"
    puts $gb
end


def method1
    puts "Inside method"
    puts $gb
end


class Some
    puts "Inside class"
    puts $gb
end 

method1

puts "Inside toplevel"
puts $gb
puts global_variables.include? :$gb

Trong đoạn code trên chúng ta có biến toàn cục $gb. Biến toàn cục có thể sử dụng được trong cả module, phương thức, lớp…

$gb = 6

Chúng ta khai báo biến toàn cục $gb và gán giá trị là 6.

module ModuleM        
    puts "Inside module"
    puts $gb
end

Trong module ModuleM chúng ta in giá trị của biến toàn cục.

def method1
    puts "Inside method"
    puts $gb
end

Trong phương thức method1 cũng vậy.

class Some
    puts "Inside class"
    puts $gb
end

Và cả bên trong class cũng vậy.

puts $gb
puts global_variables.include? :$gb

Ở ngoài các phương thức, class… chúng ta cũng có thể gọi đến biến toàn cục $gb.

Inside module
6
Inside class
6
Inside method
6
Inside toplevel
6
true

Ngoài các biến toàn cục do chúng ta định nghĩa, Ruby còn có sẵn các biến được định nghĩa sẵn.

p $LOAD_PATH
p $:

Ở trên chúng ta dùng 2 biến $LOAD_PATH$:, đây đều là các biến lưu trữ thông tin về các đường dẫn thư mục có trong biến môi trường Path của Windows.

Biến instance và biến class

Ở đây chúng ta sẽ tìm hiểu sơ qua về biến instance và biến class. Chúng ta sẽ tìm hiểu thêm trong bài hướng đối tượng.

Biến instance là biến nằm trong một đối tượng cụ thể. Mỗi đối tượng có những biến instance riêng của chúng, biến instance có tên bắt đầu bằng kí tự @.

Biến class là biến nằm trong một lớp. Những đối tượng được tạo ra từ lớp đó sẽ dùng chung biến class, biến class có tên bắt đầu bằng kí tự @@.

class Being
    
    @@is = true
       
    def initialize nm
        @name = nm
    end
    
    def to_s
        "This is #{@name}"
    end    
    
    def does_exist?
        @@is
    end
end

b1 = Being.new "Being 1"
b2 = Being.new "Being 2"
b3 = Being.new "Being 3"

puts b1, b2, b3

p b1.does_exist?
p b2.does_exist?
p b3.does_exist?

Trong đoạn code trên chúng ta định nghĩa lớp Being. Lớp này có một biến instance và một biến class.

class Being
    
    @@is = true

Biến @@is là một biến class, tất cả các đối tượng thuộc lớp Being đều dùng chung một biến @@is.

def initialize nm
    @name = nm
end

Phương thức initialize là phương thức khởi tạo. Phương thức này tự động được gọi khi chúng ta khai báo đối tượng. Mỗi đối tượng thuộc lớp Being có biến instance @name khác nhau.

def to_s
    "This is #{@name}"
end       

Phương thức to_s tự động được gọi khi dùng với hàm như p hay puts. Ở đây phương thức này trả về một chuỗi.

def does_exist?
    @@is
end

Phương thức does_exist? trả về biến class của lớp đó.

b1 = Being.new "Being 1"
b2 = Being.new "Being 2"
b3 = Being.new "Being 3"

Sau khi đã định nghĩa lớp, chúng ta khai báo 3 đối tượng thuộc lớp Beingb1, b2b3. Mỗi đối tượng khi khởi tạo được nhận một tham số dùng cho biến @name của riêng chúng.

puts b1, b2, b3

Phương thức puts sẽ tự động gọi đến phương thức to_s tương ứng với từng đối tượng.

p b1.does_exist?
p b2.does_exist?
p b3.does_exist?

Phương thức does_exist? sẽ trả về giá trị của biến @@is, do cả 3 đối tượng này đều dùng chung biến class @@is nên kết quả trả về là như nhau.

This is Being 1
This is Being 2
This is Being 3
true
true
true

Django – Cache

Website được tạo ra ngày nay là website động, tức là nội dung HTML sẽ được server sinh ra rồi trả về cho người dùng mỗi khi người dùng gửi request đến, khác với website tĩnh là các file HTML đã có sẵn, người dùng request thì chỉ cần trả về file HTML đó thôi. Điều này cũng có nghĩa là website động sẽ tốn nhiều thời gian trả lời hơn so với website tĩnh, và khi lượt truy cập website càng nhiều thì thời gian này càng tăng lên gấp nhiều lần.

Kỹ thuật cache ra đời là để cắt giảm quá trình tính toán của website để có thể cung cấp nội dung cho người dùng một cách nhanh chóng hơn, cache có nhiều loại nhưng nhìn chung thì đều tuân theo thuật toán sau đây:

user gửi request một trang web, tìm xem trang đó đã có cache hay chưa
nếu đã có cache:
    trả về cache
ngược lại:
    tạo cache
    lưu lại trang cache vừa tạo
    trả về trang cache vừa tạo

Django cung cấp sẵn hệ thống cache rất mạnh mẽ, chúng ta sẽ lần lượt đi tìm hiểu.

Thiết lập Cache

Việc thiết lập cache trong Django rất đơn giản, chỉ là cho Django biết cache sẽ được lưu ở đâu thôi bởi vì cache lưu trên RAM sẽ có hiệu suất khác hẳn so với lưu trên file.

Tất cả các thông tin cài đặt cache đều được lưu trong biến CACHE trong file settings.py, mặc định khi tạo project thì thông tin này chưa có, chúng ta phải tự thêm vô.

Lưu cache trên RAM – Memcached

Memcached đúng với cái tên của nó, là lưu nội dung trên bộ nhớ RAM của máy chủ, do đó loại cache này có tốc độ tìm kiếm cũng như trả về nhanh nhất, thích hợp cho các website lớn có lượng truy cập cao, các website như Facebook hay Wikipedia đều dùng loại cache này.

Memcached là một chương trình dạng dịch vụ (tức là chạy ngầm bên dưới hệ điều hành) được cấp một lượng RAM nhất định, cung cấp các hàm cho phép cập nhật dữ liệu trên cache, không đụng chạm gì tới đĩa cứng hoặc cơ sở dữ liệu.

Để thiết lập memcached thì chúng ta cung cấp những thông tin sau:

  • BACKEND: django.core.cache.backends.memcached.MemcachedCache hoặc django.core.cache.backends.memcached.PyLibMCCache.
  • LOCATION: theo cú pháp IP:PORT, trong đó IP là địa chỉ máy lưu cache, port là cổng tương ứng của trình dịch vụ cache.

Ví dụ:

CACHES = {
    'default': {
    'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
    'LOCATION': '127.0.0.1:11211',
    }
}

Đoạn code trên thiết lập memcache tại máy localhost, tức là lưu trên chính server đó trên port 11211.

Chúng ta cũng có thể cho chạy nhiều trình memcached trên nhiều máy:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
        '172.19.26.240:11211',
        '172.19.26.242:11211',
        ]
    }
}

Chỉ cần khai báo thêm IP và Port, ngăn cách nhau bởi dấu phẩy là được, port có thể khác nhau chứ không cần phải giống nhau.

Lưu cache trên cơ sở dữ liệu

Loại này sẽ lưu dữ liệu trong một bảng trên CSDL. Để sử dụng thì chúng ta thiết lập như sau:

  • BACKEND: django.core.cache.backends.db.DatabaseCache
  • LOCATION: tên bảng được dùng để lưu cache trong CSDL, tất nhiên phải là bảng trắng, chưa có gì trong đó

Ví dụ:

CACHES = {
    'default': {
    'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
    'LOCATION': 'my_cache_table',
    }
}

Sau khi đã khai báo trong file settings.py thì chúng ta phải tạo bảng lưu cache bằng cách chạy lệnh:

python manage.py createcachetable

Django sẽ tự động tạo bảng với tên tương ứng trong biến LOCATION cùng các trường cần thiết để lưu cache.

Lưu cache trong file

Loại cache này sẽ lưu dữ liệu trong file, khi nào cần thì sẽ đọc file. Chúng ta thiết lập như sau:

  • BACKEND: django.core.cache.backends.filebased.FileBasedCache
  • LOCATION: đường dẫn đến file

Ví dụ:

CACHES = {
    'default': {
    'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
    'LOCATION': 'c:/cache.txt',
    }
}

Đường dẫn file phải là đường dẫn tuyệt đối – tức là phải có tên ổ đĩa cứng.

Các tham số khác

Ngoài 2 tham số bắt buộc là BACKENDLOCATION thì khi thiết lập cache chúng ta còn có các tham số tùy chọn khác như sau:

  • TIMEOUT: thời gian lưu trữ cache, mặc định là 300 giây (5 phút), bạn có thể đưa vào là None và Django sẽ lưu cache vô thời hạn.
  • OPTIONS: danh sách một số tùy chọn cache, bao gồm MAX_ENTRIES là số lượng trang tối đa được phép cache, mặc định là 300; CULL_FREQUENCY là số lượng trang cache bị xóa khi số lượng trang cache đã đạt mức tối đa, tính bằng 1 / CULL_FREQUENCY, ví dụ CULL_FREQUENCY là 2 thì nếu số trang cache đã đạt đến 300 trang thì số lượng trang bị hủy là 150 trang. Nếu thiết lập CULL_FREQUENCY=0 thì xóa toàn bộ cache.
  • KEY_PREFIX: đây là một chuỗi được thêm vào đầu các khóa được lưu trong cache. Chúng ta sẽ tìm hiểu thêm về khóa ở dưới.
  • VERSION: số phiên bản cache sử dụng. Chúng ta cũng sẽ tìm hiểu ở dưới.
  • KEY_FUNCTION: tên hàm thực hiện việc tạo chuỗi key lưu trong cache.

Ví dụ:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': 'c:/cache.txt',
        'TIMEOUT': 60,
        'OPTIONS': {
            'MAX_ENTRIES': 1000
        }
    }
}

Đoạn trên thiết lập cache được lưu trong file cache.txt, thời gian mỗi trang tồn tại là 60 giây, số lượng cache tối đa là 1000.

Nếu chúng ta có lỡ thiết lập sai biến nào thì Django sẽ không báo lỗi mà bỏ qua xem biến khác, do đó khi thiết lập cache chúng ta nên kiểm tra lại cho kỹ.

Cache cả trang

Việc cache cũng có nhiều kiểu, chúng ta có thể cache từng phần hoặc cache nguyên cả trang web, nguyên trang tức là trong toàn bộ website của bạn có trang nào thì cũng đều được cache lại. Để cache cả trang web thì chúng ta khai báo các lớp sau trong biến MIDDLEWARE_CLASSES trong file settings.py:

MIDDLEWARE_CLASSES = [
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
]

Lưu ý là bạn phải khai báo đúng thứ tự như trên thì mới sử dụng được.

Lớp FetchFromCacheMiddleware sẽ lưu lại các trang web mà có mã trả về là 200 và có request được gửi lên bởi phương thức GETHEAD. Các yêu cầu đến trang web có tham số khác nhau thì được cache khác nhau, tức là giả sử chúng ta có một trang liệt kê danh sách sản phẩm được phân trang thì mỗi request với số trang khác nhau sẽ được phân trang khác nhau.

UpdateCacheMiddleware sẽ ghi một số thông tin vào cache như ngày/giờ tạo trang cache, thời gian tồn tại…

Cache từng view

Nếu cache toàn bộ website có hơi thừa thì chúng ta cũng có thể cache từng trang tùy vào từng hàm view. Để cache từng view thì chúng ta dùng hàm cache_page() trong lớp django.views.decorators.cache.

Chúng ta cũng không dùng hàm này như các hàm thông thường mà khai báo trước tên hàm view, ví dụ:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
    ...

Trước hàm này chúng ta thêm dấu @. Hàm cache_page() nhận 1 tham số là thời gian cache tồn tại tính theo giây, ở trên chúng ta cho thời gian này là 60 * 15, tức là 15 phút, bạn có thể ghi ra số giây rõ ràng luôn chứ không nhất thiết phải dùng biểu thức nhân như vậy.

Cũng giống như cache cả trang, cache trên view cũng phân biệt tham số, tức là trang có URL như localhost:8000/cache-page/1localhost:8000/cache-page/2 sẽ được cache riêng.

Cache template

Ngoài việc cache các hàm view, bạn cũng có thể cache các phần của template. Thẻ {% load cache %} sẽ tải cache về nếu có, cặp thẻ {% cache %}...{% endcache %} sẽ cache lại nội dung bên trong nó trong một khoảng thời gian, thẻ này nhận vào 2 tham số bắt buộc là tên cache và thời gian cache. Ví dụ:

{% load cache %}
{% cache 500 sidebar %}
    ...
{% endcache %}

Cache dữ liệu

Nhiều khi việc cache nguyên cả một trang web là quá thừa thãi, đôi khi còn phản tác dụng. Đối với những trang web có nội dung thay đổi liên tục và mỗi lần tải trang là một lần truy vấn một lượng dữ liệu lớn, chẳng hạn như Facebook, thì việc cache nguyên trang là không tối ưu, thay vào đó chúng ta chỉ nên cache những thứ ít thay đổi như giao diện…

Django cũng cung cấp các hàm cache cấp thấp để hỗ trợ bạn làm việc này, bạn có thể cache bất cứ kiểu dữ liệu nào của Python như string, dictionary, list…

Truy vấn dữ liệu cache

Những dữ liệu mà bạn đã cache lại sẽ được lưu trong đối tượng django.core.cache.caches, đây là một đối tượng tĩnh toàn cục, tức là bạn có thể truy xuất ở bất kỳ đâu, đối tượng này lưu dữ liệu theo dạng dictionary, tức là mỗi phần tử là một cặp khóa-giá trị, chúng ta có thể truy xuất dữ liệu cache như sau:

>>> from django.core.cache import caches
>>> cache1 = caches['key1']
>>> cache2 = caches['key1']
>>> cache1 is cache2
True

Nếu chúng ta truy xuất sai tên khóa thì Django sẽ báo lỗi exception InvalidCacheBackendError.

Thiết lập cache

Lớp django.core.cache cung cấp 2 phương thức để thiết lập và lấy dữ liệu cache là set(key,value,timeout)get(key). Ví dụ:

>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'

Tham số timeout là tùy chọn, nếu chúng ta không đưa tham số này vào thì Django sẽ sử dụng thông số được thiết lập trong file settings.py. Nếu không tìm thấy dữ liệu thì phương thức get() sẽ trả về đối tượng None.

Nhưng nếu muốn chúng ta có thể thêm tham số default vào phương thức get() và Django sẽ trả về giá trị default nếu không tìm thấy dữ liệu cache:

>>> cache.get('my_key', 'has expired')
'has expired'

Ngoài phương thức set() chúng ta còn có phương thức add() dùng để thiết lập thêm dữ liệu cache. Điểm khác nhau giữa 2 phương thức là set() sẽ cập nhật lại dữ liệu nếu cache đã tồn tại, còn add() thì không:

>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'

Nếu bạn muốn lấy một giá trị mà không biết là đã có trong cache hay chưa, bạn có thể dùng phương thức get_or_set(), đúng như cái tên của nó, là sẽ trả về giá trị nếu đã có, còn nếu chưa có thì tạo mới:

>>> cache.get('my_new_key') # returns None
>>> cache.get_or_set('my_new_key', 'my new value', 100)
'my new value'

Ngoài các dữ liệu thông thường, bạn cũng có thể truyền giá trị là một phương thức nào đó, tất nhiên là phương thức này phải tra về một giá trị nào đó:

>>> import datetime
>>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)
datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)

Nếu bạn muốn lấy nhiều giá tri cùng một lúc thì dùng phương thức get_many(), phương thức này rất hữu ích nếu chúng ta dùng phương pháp cache trên file hoặc trên cơ sở dữ liệu, vì chỉ cần đọc file cache một lần rồi lấy hết dữ liệu ra, không như phương thức get() là mỗi lần chỉ đọc một giá trị:

>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

Tương tự, chúng ta cũng có phương thức set_many() thiết lập nhiều giá trị cùng một lúc:

>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

Để xóa một giá trị cache nào đó thì chúng ta dùng phương thức delete():

>>> cache.delete('a')

Tương tự với get_many()set_many(), chúng ta có delete_many():

>>> cache.delete_many(['a', 'b', 'c'])

Nếu muốn xóa toàn bộ cache thì dùng phương thức clear():

>>> cache.clear()

Phiên bản cache

Phiên bản ở đây là chúng ta dùng các con số để đánh dấu dữ liệu cache nào thuộc về nhóm nào chứ không phải là phiên bản phần mềm cache của Django hay cái gì đó tương tự 🙂 Mặc định Django sẽ dùng số phiên bản là con số được khai báo trong file settings.py, nhưng chúng ta cũng có thể override lại trong khi viết code bằng cách đưa vào tham số version:

>>> cache.set('my_key', 'hello world!', version=2)
>>> cache.get('my_key')
None
>>> cache.get('my_key', version=2)
'hello world!'

Trong đoạn code trên chúng ta thiết lập khóa my_key với version là 2, khi chúng ta lấy giá trị của khóa này mà không chỉ định rõ version nào thì Django sẽ lấy khóa có version được lưu trong file settings.py, mặc định ở đây trong file settings.py lưu version là 1 nên đoạn code trên trả về đối tượng None.

Chúng ta cũng có thể tăng/giảm giá trị của version thông qua 2 phương thức incr_version()decr_version():

# version = 3
>>> cache.incr_version('my_key') 
>>> cache.get('my_key')
None
>>> cache.get('my_key', version=2)
None
>>> cache.get('my_key', version=3)
'hello world!' 

Ruby – Cơ bản

Trong phần này chúng ta sẽ học cách làm việc với ngôn ngữ Ruby.

Ví dụ một đoạn code Ruby:

puts "This is Ruby"

Đoạn code trên rất đơn giản, chỉ là in chuỗi “This is Ruby” ra màn hình console. Từ khóa puts nhận vào một tham số phía sau nó là một chuỗi text, chuỗi text được bọc trong cặp dấu nháy kép "".

This is Ruby

Đọc dữ liệu vào từ terminal (trong Windows là Command Promptcmd):

print "What is your name? "
name = gets

puts "Hello #{name}"

Đoạn code trên sẽ nhận một chuỗi do chúng ta nhập vào từ bàn phím rồi in ra màn hình.

print "What is your name? "

Từ khóa print cũng có tác dụng in một chuỗi text lên màn hình nhưng không xuống dòng như từ khóa puts.

name = gets

Dòng code trên sẽ đọc một đoạn text từ người dùng và lưu vào biến name bằng cách sử dụng phương thức gets,

puts "Hello #{name}"

#{name} sẽ thay bằng giá trị của biến name, bằng cách đó chúng ta có thể đưa giá trị của biến vào trong chuỗi.

What is your name? Pho Code
Hello Pho Code

Ngoài cách chạy code Ruby từ file script thì chúng ta cũng có thể chạy code từng dòng trong terminal:

C:\User\PhoCode>ruby -e "puts RUBY_VERSION"
2.2.4

Khi chúng ta chạy code Ruby từ file script, chẳng hạn như ruby hello.rb thì trình thông dịch Ruby sẽ đọc và dịch code trong file hello.rb, còn nếu chúng ta đưa vào tham số -e thì Ruby sẽ thực thi đoạn lệnh phía sau chứ không tìm file để chạy.

Ngoài ra trình thông dịch Ruby còn có tham số -c, khi dùng tham số này thì trình thông dịch Ruby sẽ thực hiện kiểm tra lỗi cú pháp của đoạn code chứ không thực hiện đoạn code bên trong, nếu không có lỗi thì sẽ in đoạn text “Syntax OK” ra màn hình.

puts "This is Ruby

Đoạn có trên có lỗi cú pháp là thiếu dấu " kết thúc chuỗi.

C:\User\PhoCode>ruby -c syntax_check.rb 
syntax_check.rb:1: unterminated string meets end of file

Trình thông dịch sẽ báo lỗi nếu có lỗi.

Đưa tham số vào chương trình

Khi chúng ta chạy một file script Ruby thì có thể đưa các tham số vào chương trình để sử dụng.

puts ARGV

Trong Ruby biến toàn cục ARGV là biến lưu các tham số được truyền vào chương trình, đây là một mảng, mỗi phần tử trong mảng là một tham số.

puts ARGV

Chúng ta in toàn bộ tham số được đưa vào chương trình.

C:\User\PhoCode>ruby args.rb 1 2 3
1
2
3

Chúng ta truyền tham số vào bằng cách gõ các giá trị vào sau tên file script. Trong đoạn code trên chúng ta truyền vào 3 con số.

Ngoài ra chúng ta có thể lấy từng tham số đơn lẻ:

puts $0
puts $*

Trong Ruby các biến toàn cục có tên bắt đầu bằng kí tự $ theo sau là tên biến. Ngoài ra Ruby còn có sẵn một số biến đặc biệt, chẳng hạn như $0 lưu tên file script được thực thi, $* cũng lưu các tham số truyền vào như ARGV.

C:\User\PhoCode>ruby args2.rb Ruby Python Perl
Ruby
Python
Perl

Biến và hằng số

Biến là nơi để lưu trữ dữ liệu. Biến gồm có tên và kiểu dữ liệu, kiểu dữ liệu là các loại giá trị khác nhau, ví dụ như số nguyên, chuỗi, số thực… là các loại kiểu dữ liệu khác nhau. Không giống các ngôn ngữ như C++, Java… bạn phải khai báo rõ ràng kiểu dữ liệu cùng tên biến như int a, Ruby là ngôn ngữ động, tức là bạn chỉ cần khai báo tên biến và gán giá trị cho nó là Ruby sẽ tự quy định kiểu dữ liệu, ngoài ra bạn cũng có thể thay đổi giá trị và Ruby cũng sẽ tự động thay đổi kiểu dữ liệu cho bạn luôn.

Hằng số trong Ruby cũng khác với các ngôn ngữ khác, trong các ngôn ngữ như C++, Java… thì hằng số là không thể thay đổi được giá trị, còn hằng số trong Ruby thì có thể thay đổi được, khi dịch thì Ruby không báo lỗi mà chỉ đưa ra các lời cảnh báo.

city = "New York"
name = "Paul"; age = 35
nationality = "American"

puts city
puts name
puts age
puts nationality

city = "London"

puts city

Trong đoạn code trên chúng ta làm việc với 4 biến.

city = "New York"

Biến city lưu trữ một chuỗi text, Ruby tự động nhận diện kiểu dữ liệu.

name = "Paul"; age = 35

Khi khai báo nhiều biến trên cùng một dòng thì chúng ta phải ngăn cách chúng bằng dấu chấm phẩy. Trong thực tế thì tốt hơn hết là chúng ta khai báo trên nhiều dòng khác nhau.

puts city
puts name
puts age
puts nationality

Chúng ta in các biến ra màn hình

city = "London"

Đoạn code trên sẽ gán lại giá trị mới cho biến city.

C:\User\PhoCode>ruby variables.rb 
New York
Paul
35
American
London

Trong đoạn code dưới đây chúng ta sẽ làm việc với hằng số.

WIDTH = 100
HEIGHT = 150

var = 40
puts var

var = 50
puts var

puts WIDTH
WIDTH = 110
puts WIDTH

Chúng ta khai báo 2 hằng và một biến.

WIDTH = 100
HEIGHT = 150

Chúng ta chỉ cần đặt tên với chữ cái đầu viết hoa thì biến đó sẽ là hằng số. Trong thực tế thì chúng ta nên viết hoa toàn bộ các chữ cái trong tên hằng.

C:\User\PhoCode>ruby constants.rb 
40
50
100
constants.rb:13: warning: already initialized constant WIDTH
110

Django – Gửi Email

Mặc định thì Python có sẵn một module hỗ trợ gửi email là smtplib nhưng Django cũng có module riêng giúp chúng ta gửi mail một cách dễ dàng và nhanh chóng là django.core.mail.

Hàm send_mail()

Cú pháp:

send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None, html_message=None)

Trong đó subject, message, from_emailrecipient_list là bắt buộc phải có.

  • subject: tiêu đề mail.
  • message: nội dung mail gửi đi.
  • from_email: địa chỉ mail dùng để gửi.
  • recipient_list: danh sách địa chỉ mail gửi tới.
  • fail_silently: nếu là True thì Django sẽ giải phóng lỗi smtplib.SMTPException nếu mail không gửi được, mặc định False.
  • auth_user: địa chỉ mail dùng để gửi, không cần đưa vào nếu đã thiết lập trong file settings.py.
  • auth_password: mật khẩu của địa chỉ mail dùng để gửi, không cần đưa vào nếu đã thiết lập trong file settings.py.
  • connection: tên đối tượng mail backend xử lý việc gửi mail, nếu không truyền vào thì Django sẽ tự động tạo một đối tượng mặc định, thường chúng ta cũng không quan tâm đến tham số này.
  • html_message: nội dung mail gửi đi dưới dạng HTML.

Hàm này sẽ trả về 1 nếu mail gửi thành công và 0 nếu thất bại.

Ví dụ:

from django.core.mail import send_mail

send_mail('Subject', 'Message', 
          'from@example.com', 
          ['to@example.com'], 
          fail_silently=False)

Các thiết lập của mail được lưu trong file settings.py, trong file này chúng ta khai báo các biến sau:

  • EMAIL_HOST: tên máy chủ mail, ví dụ smtp.google.com
  • EMAIL_PORT: số port của máy chủ mail, ví dụ 587
  • EMAIL_HOST_USER: địa chỉ email dùng để gởi đi, ví dụ from@example.com
  • EMAIL_HOST_PASSWORD: mật khẩu đăng nhập email dùng để gởi đi.
  • EMAIL_USE_TLSEMAIL_USE_SSL: True nếu muốn dùng các giao thức bảo mật SSL/TLS

Hàm send_mass_mail()

Hàm send_mass_mail() đơn giản là dùng để gửi một lúc nhiều mail.

Cú pháp:

send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None, connection=None)

Các tham số cũng giống như trong hàm send_mail() ngoại trừ tham số datatuple, tham số này nhận một đối tượng tuple, mỗi phần tử lại là một tuple khác lưu những thông tin về mail được gửi đi có dạng như sau:

(subject, message, from_email, recipient_list)

Ví dụ:

message1 = ('Subject 1', 
            'Message 1', 
            'from@example.com', 
            ['first@example.com', 'other@example.com'])
message2 = ('Subject 2', 
            'Message 2', 
            'from@example.com', 
            ['second@test.com'])
send_mass_mail((message1, message2), fail_silently=False)

Hàm send_mass_mail() cũng trả về 0 hoặc 1 tương ứng với thành công hoặc thất bại.

Sự khác nhau giữa send_mail()send_mass_mail()send_mail() khi gửi mail nào thì phải mở và đóng kết nối tới server SMTP, trong khi send_mass_mail() chỉ cần mở một kết nối rồi gửi tất cả luôn, do đó send_mass_mail() hiệu quả hơn send_mail().

Hàm mail_admins()

Hàm này có tác dụng gửi mail cho ban quản trị website, mail của quản trị website (admin) được liệt kê trong biến ADMINS trong file settings.py dưới dạng:

...
ADMINS = [('John', 'john@example.com'), ('Mary', 'mary@example.com')]
...

Cú pháp:

mail_admins(subject, message, fail_silently=False, connection=None, html_message=None)

Lưu ý: đoạn chuỗi được lưu trong biến EMAIL_SUBJECT_PREFIX trong file settings.py sẽ được chèn vào trước tham số subject, mặc định biến này có giá trị ” [Django] “.

Các thông tin khác như server, port, mail người gửi sẽ được dùng trong file settings.py.

Hàm mail_managers()

Hàm này có công dụng giống như hàm mail_admins(), chỉ khác là gửi cho danh sách mail trong biến MANAGERS trong file settings.py.

Cú pháp:

mail_managers(subject, message, fail_silently=False, connection=None, html_message=None)

Lớp EmailMessage

Các hàm send_mail() hay send_mass_mail() chỉ là các hàm cấp cao hỗ trợ chúng ta gửi mail cho dễ dàng, thực chất việc gửi mail được thực hiện qua lớp EmailMessage.

Cũng chính vì thế mà có nhiều tính năng có trong lớp EmailMessagemà chúng ta không sử dụng được với 2 hàm trên chẳng hạn như BCC, gửi file đính kèm, gửi nội dung đa phương tiện…

Bản chất thì lớp EmailMessage chỉ làm công việc là tạo nội dung email sẽ được gửi đi, còn phần gửi được thực hiện bởi email-backend phía dưới nữa. Lớp EmailMessage cũng chỉ hỗ trợ gửi từng mail đơn lẻ với phương thức send(). Để gửi nhiều mail cùng một lúc thì chúng ta phải can thiệp vào backend.

Lớp EmailMessage có các thuộc tính và cũng là tham số trong hàm khởi tạo như sau:

  • subject: tiêu đề mail
  • body: nội dung mail
  • from_email: địa chỉ mail gửi đi
  • to: danh sách các mail gửi tới
  • bcc: danh sách các địa chỉ được dùng trong Bcc
  • connection: đối tượng backend, nếu chúng ta không khai báo thì Django sẽ tự tạo một đối tượng mặc định
  • attachment: danh sách các file đính kèm, chúng ta có thể dùng lớp email.MIMEBase.MIMEBase hoặc khai báo theo dạng (filename, content, mimetype).
  • headers: một đối tượng dictionary dùng cho header của mail.
  • cc: list hoặc tuple các địa chỉ mail dùng cho Carbon Copy (Cc).
  • reply_to: list hoặc tuple địa chỉ mail dùng khi trả lời mail.

Ví dụ:

from django.core.mail import EmailMessage

email = EmailMessage('Hello',
                     'Body',
                     'from@example.com',
                     ['to1@example.com', 'to2@example.com'],
                     ['bcc@example.com'],
                     reply_to=['another@example.com'],
                     headers={'Message-ID': 'foo'})

Lớp EmailMessage có các phương thức sau đây:

  • send(fail_silently=False) sẽ gửi mail đi. Tham số fail_silently sẽ giải phóng lỗi exception nếu là True, ngược lại (và mặc định) là False.
  • message() khởi tạo một đối tượng django.core.mail.SafeMIMEText hoặc django.core.mail.Safe.MIMEMultipart, đây là các lớp kế thừa từ lớp email.MIMEText.MIMEText trong Python, có nhiệm vụ lưu thông tin về nội dung mail được gửi đi.
  • recipients() trả về các danh sách địa chỉ mail có trong đối tượng EmailMessage, bất kể là to, bcc, hay cc...
  • attach(): gửi file đính kèm, chúng ta có thể truyền vào một đối tượng email.MIMEBase.MIMEBase hoặc một tuple có dạng (filename, content, mimetype). Ví dụ:
message.attach('design.png', img_data, 'image/png')
  • attach_file() cũng gửi file đính kèm với tham số là đường dẫn đến file trong máy. Ví dụ:
message.attach_file('images/weather_map.png') 

Django – Ngôn ngữ Template

Trong phần này chúng ta sẽ tìm hiểu kỹ hơn về cú pháp của Template trong Django.

Ngôn ngữ Template của Django được thiết kế với mục đích chính là hỗ trợ những người đã từng làm việc với HTML, do đó nếu bạn đã từng học HTML thì sẽ không quá khó khăn để làm quen với Template.

Nếu bạn đã từng làm việc với các ngôn ngữ như Javascript, PHP, JSP… hay các ngôn ngữ có thể trộn chung với code HTML thì bạn cũng nên phân biệt là Template của Django không giống các ngôn ngữ đó. Các ngôn ngữ như Javascript, PHP… là ngôn ngữ lập trình, dùng để thực hiện các công việc mang tính logic, còn HTML chỉ là ngôn ngữ đánh dấu, tức là chỉ dùng để hiển thị giao diện chứ không mang nặng phần tính toán, Template cũng vậy, đây chỉ là ngôn ngữ hỗ trợ hiển thị giao diện.

Hệ thống Template của DJango cung cấp các thẻ có các chức năng tương tự như các câu lệnh trong Python, chẳng hạn như thẻ if dùng để kiểm tra điều kiện, thẻ for dùng trong vòng lặp… các thẻ này cũng không hoạt động giống như trong Python. Khi dịch thì Django chỉ dịch các thẻ Template chứ không đụng chạm gì tới HTML.

Template

Một Template đơn giản chỉ là một file text, có thể là bất cứ định dạng nào như .html, .xml, .csv…v.v

Template chứa các biến sẽ được thay thế bằng giá trị thực khi dịch, và các thẻ dùng để thực hiện các câu lệnh logic.

Đây là một đoạn template cơ bản:

{% extends "base_generic.html" %}

{% block title %}
    {{ section.title }}
{% endblock %}

{% block content %}
    <h1>{{ section.title }}</h1>
    {% for story in story_list %}
    <h2>
        <a href="{{ story.get_absolute_url }}">
            {{ story.headline|upper }}
        </a>
    </h2>
    {{ story.tease|truncatewords:"100" }}
    {% endfor %}
{% endblock %}

Biến

Biến là những thứ giống như {{ variable }}. Khi trình dịch template đọc đến một biến thì biến sẽ được thay thế bằng một giá trị thật (mà chúng ta truyền vào từ hàm render() trong các view). Biến chỉ được đặt tên bằng các kí tự chữ cái và dấu gạch dưới (_).

Chúng ta dùng dấu chấm (.) để truy xuất các thuộc tính của biến.

Trong đoạn code trên, {{ section.title }} sẽ được thay thế bởi thuộc tính title của đối tượng section.

Nếu bạn gõ sai tên biến thì Django sẽ thay bằng chuỗi rỗng chứ không báo lỗi.

Bộ lọc – Filter

Django cung cấp các bộ lọc để hỗ trợ chúng ta hiển thị dữ liệu theo nhiều cách khác nhau.

Ví dụ {{ name|lower }}trong đó lower là một bộ lọc, có tác dụng chuyển toàn bộ chữ cái thành chữ thường. Để dùng các bộ lọc thì chúng ta kèm theo dấu | và tên bộ lọc vào sau tên biến.

Chúng ta cũng có thể dùng nhiều bộ lọc cùng một lúc, các bộ lọc được thực hiện tuần tự từ trái sang phải, ví dụ {{ text|escape|linebreaks }} có tác dụng xuống dòng sau khi in dữ liệu.

Một số bộ lọc cần có cả tham số nữa, ví dụ như {{ bio|truncatewords:30 }} có nghĩa là lấy 30 từ đầu tiên của biến bio.

Nếu tham số của bộ lọc có khoảng trống thì chúng ta phải kẹp chúng trong cặp dấu nháy kép “”. Ví dụ {{ list|join:", " }} sẽ nối các item trong biến list thành một string, ngăn cách nhau bởi dấu phẩy và dấu cách.

Django có khoảng 60 bộ lọc. Bạn có thể tìm hiểu chúng tại đây. Ở đây mình chỉ giới thiệu một số bộ lọc thường dùng:

  • default: nếu biến không có giá trị hoặc giá trị rỗng thì thay thế bằng giá trị default. Ví dụ {{value|default:"nothing"}}
  • length: trả về độ dài của dữ liệu, có thể áp dụng cho string và list. Ví dụ {{value|length}}
  • filesizeformat: đổi kiểu số thành định dạng file, ví dụ {{value|filesizeformat}} sẽ chuyển con số 123456789 thành 117.7 MB.

Thẻ – Tag

Thẻ có cú pháp {% tag %}. Thẻ thì phức tạp hơn biến một tí, có thể dùng để tạo chuỗi, thực hiện các luồng điều khiển hoặc load các thông tin khác vào template.

Có một số thẻ đi kèm với cả thẻ kết thúc nữa, ví dụ {% tag %} thì sẽ có {% endtag %}.

Cũng giống như các bộ lọc, số lượng thẻ trong Django rất nhiều, bạn có thể xem danh sách các thẻ ở đây. Trong bài này mình cũng chỉ giới thiệu các thẻ thường dùng:

  • for: duyệt qua một đối tượng danh sách. Ví dụ:
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
  • if, elifelse: kiểm tra một biến, nếu biến đúng thì thực hiện đoạn code bên trong.
{% if athlete_list %}
    Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
    Athletes should be out of the locker room soon!
{% else %}
    No athletes.
{% endif %}

Trong đoạn code trên, nếu athlete_list không rỗng thì in ra biến athlete_list, ngược lại thì kiểm tra nếu athlete_in_locker_room_list không rỗng thì in ra đoạn text “Athletes should…”, còn nếu không thì in ra đoạn text “No athletes.”

Ngoài kiểm tra các biến thì bạn cũng có thể áp dụng bộ lọc vào biến khi dùng thẻ if:

{% if athlete_list|length > 1 %}
    Team: {% for athlete in athlete_list %} ... {% endfor %}
{% else %}
    Athlete: {{ athlete_list.0.name }}
{% endif %}

Hầu hết các bộ lọc chỉ trả về giá trị là kiểu chuỗi nên thường sẽ không dùng được các biểu thức so sánh với số nguyên như trên, length chỉ là một trong số ít ngoại lệ.

  • blockextends: kế thừa template, tức là dùng các file template khác. Chúng ta sẽ tìm hiểu thêm sau.

Bình luận – Comment

Bình luận được đặt trong cặp dấu {# #}, các đoạn code bên trong cặp dấu này sẽ không được thực thi, ví dụ:

{# greeting #}hello

Django chỉ hỗ trợ bình luận trên một dòng. Nếu muốn bình luận trên nhiều dòng thì bạn dùng thẻ comment.

Thừa kế template

Tính năng mạnh mẽ nhất và cũng là phức tạp nhất của Template trong Django là tính năng thừa kế. Tính năng thừa kế cho phép bạn xây dựng một bộ template tổng quát và các template con, trong đó template tổng quát sẽ chứa các template con.

Ví dụ:

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="style.css" />
    <title>{% block title %}My amazing site{% endblock %}</title>
</head>

<body>
    <div id="sidebar">
        {% block sidebar %}
        <ul>
            <li><a href="/">Home</a></li>
            <li><a href="/blog/">Blog</a></li>
        </ul>
        {% endblock %}
    </div>

    <div id="content">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

Đoạn code trên là template thiết kế bộ khung của một trang web, cấu trúc của template này bao gồm 2 cột, nằm giữa các thẻ block. Nhiệm vụ của các template con là lấp đầy các khoảng trống của 2 cột đó.

Trong đoạn code trên có 3 thẻ block là title, contentsidebar, nhiệm vụ của thẻ block là báo cho Django biết đây là nơi mà các template con có thể override lại và chèn dữ liệu cần hiển thị vào đó.

Ví dụ về một template con:

{% extends "base.html" %}

{% block title %}My amazing blog{% endblock %}

{% block content %}
    {% for entry in blog_entries %}
        <h2>{{ entry.title }}</h2>
        {{ entry.body }}
    {% endfor %}
{% endblock %}

Để một template con có thể override lại các thẻ block của template khác thì ở đầu template chúng ta khai báo thẻ extends với tên file template. Trong ví dụ trên, trình biên dịch Django sẽ đọc trong template cha và thấy các thẻ block trong template cha cũng có trong template con nên phần block trong template con sẽ được chèn vào trong template cha.

Trong ví dụ trên thì tùy thuộc vào giá trị của blog_entries mà kết quả là template cha có thể sẽ có nội dung như sau:

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="style.css" />
    <title>My amazing blog</title>
</head>

<body>
    <div id="sidebar">
        <ul>
            <li><a href="/">Home</a></li>
            <li><a href="/blog/">Blog</a></li>
        </ul>
    </div>
    <div id="content">
        <h2>Entry one</h2>
        This is my first entry.
        <h2>Entry two</h2>
        This is my second entry.
    </div>
</body>
</html>

Trong đoạn code template con chúng ta chỉ định nghĩa 2 block là contenttitle, nếu chúng ta không override block sidebar thì nội dung của sidebar sẽ được dùng là nội dung trong template cha.

Một số lưu ý:

  • Thẻ {% extends %} luôn được đặt trước tất cả các thẻ còn lại.
  • Nên override từng thẻ block trong từng file template chứ không nên “ôm” tất cả vào trong một file.
  • Thẻ block trong template con cũng có thể dùng lại nội dung của template cha, chỉ cần gọi {{block.super}}
  • Thẻ {% endblock %} không cần phải có tên block theo sau nhưng chúng ta cũng nên đưa tên block vào để code dể đọc và dễ quản lý hơn. Ví dụ:
{% block content %}
...
{% endblock content %}
  • Không được có 2 thẻ block có tên giống nhau.

Tự động thoát HTML

Thoát HTML tức là trang web tự động chuyển đổi các kí tự đặc biệt trong HTML sang một dạng mã, dùng để bảo vệ website.

Ví dụ chúng ta có đoạn code template như sau:

Hello, {{ name }}

Thoạt nhìn thì có vẻ đơn giản, chúng ta có thể yêu cầu người dùng nhập vào một textbox rồi lưu vào biến name, sau đó in nội dung trong biến name ra thôi.

Nhưng nếu người dùng không nhập vào biến name một đoạn chuỗi bình thường mà là đoạn chuỗi kì lạ như:

<script>alert('hello')</script>

Lúc này Django sẽ dịch đoạn template sang đoạn code HTML như sau:

Hello, <script>alert('hello')</script>

Khi chạy, trang web sẽ hiển thị một hộp thoại thông báo. Đó chỉ là trường hợp đơn giản, trong thực tế hacker có thể lợi dụng lỗ hổng này để khai thác nhiều thứ hơn nữa. Đây gọi là kỹ thuật tấn công Cross Site Scripting (XSS).

May mắn là mặc định trình dịch Template của Django tự động “thoát” (auto-escape) các kí tự đặc biệt, tức là chuyển đổi những kí tự sau đây thành những kí tự mã khác:

  • Dấu < chuyển thành &lt;
  • Dấu > chuyển thành &gt;
  • Dấu nháy đơn ' chuyển thành &#39;
  • Dấu nháy kép " chuyển thành &quot;
  • Dấu & chuyển thành &amp;

Vì tính năng tự động thoát này mà bạn không cần phải lo đến vấn đề bảo mật XSS nữa.

Nhưng nếu bạn muốn tắt tính năng này thì làm sao?

Trước hết là tại sao bạn lại muốn tắt tính năng này, đó là vì trong một số trường hợp bạn thật sự muốn in các đoạn mã HTML, Javascript… lên trang web, chẳng hạn như bạn dự định xây dựng một blog về lập trình, như blog Phở Code 🙂 thì việc đăng các đoạn code lên trang web là thường xuyên, và do đó bạn cần các kí tự HTML hiển thị nguyên gốc – tức là không được “thoát”.

Để tắt “thoát” trên từng biến: chúng ta dùng bộ lọc safe:

This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}

Đoạn code trên sẽ cho ra HTML như sau:

This will be escaped: &lt;b&gt;
This will not be escaped: <b>

Tắt “thoát” trên template: chúng ta đặt nội dung file template hoặc một phần nào đó của template trong cặp thẻ autoescape:

{% autoescape off %}
    Hello {{ name }}
{% endautoescape %}

Thẻ autoescape nhận tham số on hoặc off tương ứng với bật và tắt.

Bạn cũng có thể lồng các cặp thẻ autoescape vào nhau.

Auto-escaping is on by default. Hello {{ name }}
{% autoescape off %}
    This will not be auto-escaped: {{ data }}.

    Nor this: {{ other_data }}
    {% autoescape on %}
        Auto-escaping applies again: {{ name }}
    {% endautoescape %}
{% endautoescape %}

khi kế thừa template thì nếu template cha tắt “thoát” thì các template con cũng sẽ tự tắt tính năng này, nếu muốn bật tính năng này thì template con phải override lại.

Gọi phương thức

Bạn không chỉ có thể truy xuất dữ liệu từ các thuộc tính trong các biến mà còn có thể gọi phương thức của chúng nữa, tất nhiên là bạn chỉ có thể gọi các phương thức có trả về dữ liệu để hiển thị chứ không thể gọi các phương thức thực hiện tính toán mà không trả về thứ gì được.

Ví dụ, các đối tượng Queryset có phương thức count() trả vê số lượng phần tử của nó, chúng ta có thể gọi phương thức này như sau:

{{ task.comment_set.all.count }}

Bạn cũng có thể gọi các phương thức do bạn tự định nghĩa:

class Task(models.Model):
    def foo(self):
        return "bar"
{{ task.foo }}

Đáng tiếc là bạn không thể truyền tham số vào các lời gọi hàm trong Template vì mục đích chính của template cũng chỉ là hiển thị dữ liệu chứ không phải tính toán, do đó bạn chỉ có thể gọi các phương thức không có tham số.