Scope (context) trong ruby
Scope mà tôi muốn nhắc đến không phải là class method của ActiveRecord scope :red , - > { where ( color : 'red' ) } # không phải cái này Scope ở đây bạn có hình dung đến 2 vấn đề thứ nhất đó là các biến, thứ 2 đó là khả năng truy cập. Hiểu được Scope bạn sẽ biết được ...
- Scope mà tôi muốn nhắc đến không phải là class method của ActiveRecord
scope :red, -> { where(color: 'red') } # không phải cái này
- Scope ở đây bạn có hình dung đến 2 vấn đề thứ nhất đó là các biến, thứ 2 đó là khả năng truy cập. Hiểu được Scope bạn sẽ biết được các biến, hằng số, các phương thức có khả dụng tại một thời điểm nào đó hay hay ngữ cảnh nào đó hay không.
- Scope ở đây nó giải quyết vấn đề naming trong code của bạn, Giả sử trong ruby không có khái niệm scope, tất cả các biến có thể được sử dụng như nhau ở mọi nơi, ôi không điều đó thật nguy hiểm các biến đó sẽ được thay đổi ở bất cứ đâu bất cứ model nào, quá khó để biết được biến nào được sử dụng biến nào chưa => bạn sẽ không kiểm soát được nó.
- Scope sẽ thay đổi bắt cứ khi nào nó gặp từ khóa class, module, method.
Ruby có các kiểu biến khác nhau:
- Class variable: Được bắt đầu bằng 2 ký từ @, nó được dùng trong class và các class con
@@class_variable = :best
- Instance variable: Được bắt đầu bằng ký tự @, nó chỉ được dùng trong một object cụ thể thông qua các phương thức của một object. Nó không được dùng trực tiếp trong class definitions.
@instance_variable = :best
- Globel variable: Được bắt đầu bằng ký tự $, nó dùng ở bất cứ mọi nơi
- Local variable: Phạm vi sử dụng của nó phụ thuộc vào scope
locale_variable = :best
- Local variable trong Scope
Giả sử trong context của method tôi có ví dụ như sau:
class A def first_method if false a = :best end puts a end end
Nếu tôi gọi A.new.first_method thì điều gì sẽ xảy ra? sẽ có người nghĩ nó sẽ bị chết ở dòng puts a vì biến a chưa được đinh nghĩa giờ hãy chạy trên console xem sao
2.1.5 :016 > A.new.first_method nil => nil
Không có lỗi gì ở đây cả, tại sao vậy? Điều này là vi trong quá trình biên dịch của ruby trong cùng một scope, local variable sẽ tự động được gán giá trị gì đó cho dù code đó có chạy hay không.
Giờ hãy thử với một ví dụ khác
class A def first_method [1,2].each do |c| puts c x = 1 end puts x end end
Tương tự như trên ta sẽ chạy câu lệnh A.new.first_method Kết quả
2.1.5 :010 > A.new.first_method 1 2 NameError: undefined local variable or method `x' for #<A:0x0000000233bdf8> from (irb):7:in `first_method' from (irb):10
Biến x trong vòng lặp khác biến x bên ngoài vòng lặp, 2 biến ở 2 scope khác nhau. nên nó không được gán trong qúa trình biên dịch và gây ra lỗi.
1. Khái niệm
- Method, module, class definition được xem như là scope gates, vì khi định nghĩa một phương thức mới một class hay module mới thì một scope mới sẽ được tạo ra.
Xét ví dụ sau:
var1 = 1 class A # start new scope def initialize var2 = 2 p local_variables end def first_method # clean old scope and start new scope var3 = 3 p local_variables end # end scope end # end scope
Thực hiện lệnh sau
a = A.new a.first_method
Kết quả sẽ như sau:
2.1.5 :046 > a = A.new [:var2] => #<A:0x00000002208be8> 2.1.5 :047 > a.first_method [:var3] => [:var3] 2.1.5 :048 >
- Trong hàm init chỉ in ra biến local là var2 mà không in ra [var1, var2] là vì var1 ở scope khác top level scope còn var2 nằm trong scope của method init, tương tự khi gọi a.first_method chỉ có var3 mà không có var2.
- Khi bắt đầu một class mới thì các local variable của top level sẽ bị tạm thời mất(không phải mất vĩnh viễn) đề vào một scope mới đi cho đến khi gặp từ khóa kết thúc end của class, kết thúc một sope.
2. Break Scope gates
Vậy làm thế nào để không có rào cản các biến local giữa các Scope gates giữa module, class, method? Có thể dùng một trong những cách sau:
- class: Class.new
- module: Module.new
- method: define_method
Xét ví dụ sau:
var1 = 1 Student = Class.new do var2 = 2 p local_variables define_method(:info) do var3 = 3 p local_variables end end
2.0.0-p598 :010 > st = Student.new => #<Student:0x000000017dd8f8> 2.0.0-p598 :011 > st.info [:var3, :var2, :var1, :_] => [:var3, :var2, :var1, :_]
Có thể thấy được 3 biến local đã được in ra khác với ví dụ trước, Đó là do trong ví dụ ta đã sử dụng break scope gates
3. Block
Block cũng là Scope Gates. Xét ví dụ sau:
2.0.0-p598 :001 > temp = :some_thing => :some_thing 2.0.0-p598 :002 > [1,2,3].each do |i| # bắt đầu block scope 2.0.0-p598 :003 > puts temp 2.0.0-p598 :004?> new_var = 'new varialbe' 2.0.0-p598 :005?> end some_thing some_thing some_thing => [1, 2, 3] 2.0.0-p598 :006 > p temp :some_thing => :some_thing 2.0.0-p598 :007 > p new_var NameError: undefined local variable or method `new_var' for main:Object from (irb):7
Có thể thấy new_var là một biến local của block và nó chỉ được sử dụng ở bên trong block.
Xét biến temp nó được in ra và không có một lỗi nào, vậy biến temp có thể được thay đổi giá trị bên trong scope của block.
xét ví dụ tiếp
2.0.0-p598 :008 > t2 = 1 => 1 2.0.0-p598 :009 > [1,2,3].each do |i| 2.0.0-p598 :010 > t2 += i 2.0.0-p598 :011?> end => [1, 2, 3] 2.0.0-p598 :012 > t2 => 7
Làm thế nào để các biến bên trong block không thay đổi các biến bên ngoài scope đó? block-local variable có thể giải quyết vấn đề đó
Để định nghĩa block-local variable chỉ cần đặt các biến đó sau dấu ; của block parameters
2.0.0-p598 :017 > t2 = 1 => 1 2.0.0-p598 :018 > [1,2,3].each do |i; t2| 2.0.0-p598 :019 > t2 = 10 2.0.0-p598 :020?> end => [1, 2, 3] 2.0.0-p598 :021 > t2 => 1
Lúc này t2 không làm thay đổi giá trị của các biến local bên ngoài scope của block.
1 class Student 2 p self 3 def info 4 p self 5 end 6 end
self ở dòng 2 là Student vì nó nằm trong scope của class Student còn self ở dòng 4 là một thể hiện của class Student
Ví dụ tiếp theo
1 outside = :hello 2 class Student 3 ag = 10 4 def info 5 name = :my_name 6 end 7 end 8 outside1 = :world 9 p local_variables 10
Kết quả [:outside, :outside1] tại sao lại như vậy? là vì khi đi vào class Student Scope mới được mở ra đồng thời scope của class Student sẽ tạm thời bị clean đi, sau khi kết thúc class Student lúc này sẽ đóng scope của Student lại đồng thời mở lại scope của top level
Ví dụ tiếp
Hãy sửa lại sao cho đoạn code này hết lỗi
name = "Maria" def info "Her name is #{name}" end info()
Áp dụng break scope gates để giải quyết
name = "Maria" define_method(:info) do "Her name is #{name}" end info()
Tương tự với ví dụ này bạn có thể dùng break scope để refactor codes Class.new
her_name = "Maria" class Student p her_name end
- Metaprogramming ruby
- http://sitepoint.com