07/01/2019, 14:42

Sức mạnh của Pry trong Rails

Hầu hết tất cả dev khi code project chạy bằng Rails đều sử dụng Pry là công cụ debug, cũng có các lệnh debug khác như là byebug nhưng Pry vẫn được sử dụng nhiều hơn cả. Để cài đặt ta dùng lệnh gem 'pry-rails', :group => :development Câu nói thần thánh khi tìm lỗi đó là mở code và đặt ...

Hầu hết tất cả dev khi code project chạy bằng Rails đều sử dụng Pry là công cụ debug, cũng có các lệnh debug khác như là byebug nhưng Pry vẫn được sử dụng nhiều hơn cả.

Để cài đặt ta dùng lệnh

gem 'pry-rails', :group => :development

Câu nói thần thánh khi tìm lỗi đó là mở code và đặt binding.pry. Vậy binding.pry là gì?

Đó là lệnh khi trình biên dịch Ruby chạy tới dòng mà có lệnh đấy, thì nó sẽ tạm dừng xử lý và bắt đầu một trạng thái REPL (read-evaluate-print-loop), cho phép chúng ta gõ bất cứ code Ruby nào, sử dụng bất cứ biến nào ở những dòng trước đấy và hiển thị kết quả ngay trên terminal, kể cả bất cứ lệnh ruby hay các câu truy vấn database, thật tuyệt vời, nhờ đó giúp chúng ta dễ dàng debug hơn bao giờ hết.

Nhưng chúng ta đã từng tự hỏi lệnh đấy từ đâu đến không? Khi bật console, ta thấy

$ rails console
pry(main)> binding
=> #<Binding:0x007fee1297a308>
pry(main)> binding.class
=> Binding

như vậy, binding là 1 object, thuộc class Binding. Nhưng nó có thật là từ gem Pry không? nếu như bỏ gem pry-rails

$ DISABLE_PRY_RAILS=1 rails console
irb(main)> binding
=> #<Binding:0x007fb29c85c4a0>

vậy là object binding không thuộc gem pry-rails, ta sử dụng IRB

$ irb
irb(main)> binding
=> #<Binding:0x007fce81825f30>

thật ngạc nhiên khi binding là 1 object thuộc Ruby chứ không phải Rails.

Một vài tính năng hữu ích của Pry

Khi vào rails console, ta bắt đầu vào môi trường Pry

Pry có một vài lệnh rất hữu ích để ta tìm hiểu code, vd như show-doc

show-doc

pry(main)> show-doc binding
From: proc.c (C Method):
Owner: Kernel
Visibility: private
Signature: binding()
Number of lines: 10
Returns a Binding object, describing the variable and
method bindings at the point of call. This object can be used when
calling eval to execute the evaluated command in this
environment. See also the description of class Binding.
def get_binding(param)
  return binding
end
b = get_binding("hello")
eval("param", b)   #=> "hello"

show-doc trả về tài liệu mô tả hàm mà chúng ta truyền vào, rất hữu ích để hiểu rõ công dụng của 1 hàm mới gặp.

Trong vd trên, ta thấy được rằng, binding thực sự là một object dùng để miêu tả một biến và trạng thái của nó tại vị trí gọi hàm.

vậy với binding.pry thì sao

pry(main)> show-doc binding.pry
From: (...)/gems/pry-0.10.4/lib/pry/core_extensions.rb @ line 23:
Owner: Object
Visibility: public
Signature: pry(object=?, hash=?)
Number of lines: 18
Start a Pry REPL on self.
If `self` is a Binding then that will be used to evaluate expressions;
otherwise a new binding will be created.
param [Object] object  the object or binding to pry
                        (__deprecated__, use `object.pry`)
param [Hash] hash  the options hash
example With a binding
   binding.pry
example On any object
  "dummy".pry
example With options
  def my_method
    binding.pry :quiet => true
  end
  my_method()
@see Pry.start

ls

ta thấy, Pry chỉ là một hàm để bắt đầu một Pry REPL thông qua đối tượng binding.

Chúng ta có thể Pry mọi object. VD

pry(main)> our_string = "some TEXT!"
pry(main)> our_string.pry
pry("some TEXT!")>

pry("some TEXT!")> self
=> "some TEXT!"
pry("some TEXT!")> upcase
=> "SOME TEXT!"
pry("some TEXT!")> lowercase
NameError: undefined local variable or method `lowercase' for "some TEXT!":String
from (pry):32:in `__pry__'
pry("some TEXT!")> ls
(...)
Comparable#methods: <  <=  >  >=  between?
Colorize::InstanceMethods#methods: colorize  colorized?  uncolorize
String#methods:
%  classify    gray     mb_chars  rpartition  to_c
*  clear       grayish  next      rstrip      to_d
+  codepoints  green    next      rstrip!     to_date
ActiveSupport::ToJsonWithActiveSupportEncoder#methods: to_json
self.methods: __pry__  
(...)
pry("some TEXT!")> ls -G case
String#methods: camelcase casecmp downcase downcase! swapcase  swapcase! titlecase upcase upcase!
pry("some TEXT!")> downcase
=> "some text!"
pry("some TEXT!")> exit
=> nil
pry(main)> self
=> main

có rất nhiều lệnh hữu ích ở trên, khi gọi self , thì sẽ trả về đối tượng chúng ta Pry, gọi hàm upcase thì sẽ viết hoa object string, hàm lowercase thì không tồn tại, ta có thể

sử dụng ls để lấy tất cả method của 1 object, nó tương tự như hàm methods, nhưng ưu điểm ở chỗ là sẽ phân nhóm method theo class và namespace, nhờ vậy ta sẽ hiểu code sâu hơn, biết method này là của class nào.

Sử dụng thêm tham số -G để giảm bớt kết quả, lệnh ls -G case sẽ trả về tất cả method có chữ case, khi đó sẽ giúp chúng ta nhanh chóng tìm ra đúng tên hàm vì nhiều lúc ta không thể nhớ hết chính xác từng tên hàm được. Lệnh này tương tự khi ta dùng với grep string.methods | grep case

1 cách khác để tìm các method đó là sử dụng find-method, thay vì phải pry 1 object , rồi chạy lệnh ls, ta có thể lấy ra các method của object đó trực tiếp từ main pry

pry(main)> find-method case our_string
String
String#camelcase
String#titlecase
String#casecmp
String#upcase
String#downcase
String#swapcase
String#upcase!
String#downcase!
String#swapcase!

cd

thay vì sử dụng string.pry, ta dùng cd, ưu điểm ở chỗ là cd có thể pry vào object theo kiểu lồng nesting

pry(main)> our_hash = { first: [1, 2, 3], second: "text" }
=> { :first => [1, 2, 3], :second => "text" }
pry(main)> cd our_hash
pry(#<Hash>):1> self
=> { :first => [1, 2, 3], :second => "text" }
pry(#<Hash>):1> cd self[:first]
pry(#<Array>):2> self
=> [1, 2, 3]
pry(#<Array>):2> nesting
Nesting status:
--
0. main (Pry top level)
1. #<Hash>
2. #<Array>
pry(#<Array>):2> jump-to 1
pry(#<Hash>):1> self
=> { :first => [1, 2, 3], :second => "text" }
pry(#<Hash>):1> cd self[:first]
pry(#<Array>):2> nesting
Nesting status:
--
0. main (Pry top level)
1. #<Hash>
2. #<Array>
pry(#<Array>):2> cd ..
pry(#<Hash>):1> self
=> { :first => [1, 2, 3], :second => "text" }
pry(#<Hash>):1> nesting
Nesting status:
--
0. main (Pry top level)
1. #<Hash>
pry(#<Hash>):1> cd /
pry(main)>

show-source

đây là 1 lệnh tôi rất thích, vì nhờ đó mà giúp tôi biết 1 hàm này thực hiện như thế nào, chứ ko phải mò mẫm chạy thử, hay phải lên mạng search cách sử dụng

pry(main)> show-source Array
From: (...)/lib/active_support/core_ext/array/access.rb @ line 1:
Class name: Array
Number of monkeypatches: 13. Use the `-a` option to display all available monkeypatches
Number of lines: 64
class Array
  # Returns the tail of the array from +position+.
(...)
pry(main)> show-source Array#second
From: (...)/lib/active_support/core_ext/array/access.rb @ line 33:
Owner: Array
Visibility: public
Number of lines: 3
def second
  self[1]
end

với tham số -a, thì sẽ hiển thị tất cả các monkeypatches mà object có, giúp chúng ta sử dụng method cẩn thận hơn.

play

khi chúng ta debug trong controller, model hay object, nếu gặp phải 1 đoạn code nhiều dòng thì sẽ mất công phải copy từng dòng để check, thay vào đó ta có thể sử dung play

From: (...)/application_controller.rb @ line 7 ApplicationController#controller_method:
5: def controller_method
    6:   binding.pry
 => 7:   puts "lots of code with"
    8:   puts "nestings and loops..."
    9: end
pry(#<ApplicationController>)> play -l 7..8
lots of code with
nestings and loops...

nhờ vậy ta có thể chạy nhiều dòng một cách tuần tự.

show-model

Đó là 1 lệnh cực kỳ hữu ích khi ta muốn kiểm tra các attribute của 1 model, thay vì phải vào tìm trong file schema hoặc trong file model(trong trường hợp dùng gem để build các attribute vào trong file model)

pry(main)> show-model User
User
  id: integer
  birthday: datetime
  address_name: string
  created_at: datetime
  updated_at: datetime
  device_id: integer
  province_id: integer
  total_completed_order: integer
  belongs_to :device
  belongs_to :province
  has_many :devices
  has_many :orders
  has_many :received_notifications (class_name :Notification, foreign_key :targetable_id)

vâng, kết quả trả về là toàn bộ attribute và các relation của model đó, rất dễ kiểm tra.

ngoài ra , nếu sử dụng thêm option -G

pry(main)> show-models -G name
User
  id: integer
  content: text
  author_name: string  # This will be highlighted due to the "name" grep
  belongs_to: author

Province:
  id: integer
  name: string # This will be highlighted due to the "name" grep

sẽ trả ra tất cả các model có attribute chứa keyword chúng ta cần tìm.

show-routes

thay vì phải ra terminal, chạy lệnh rake routes | grep path đợi khá lâu để lấy path vì nó phải load toàn bộ code thì ta có thể dùng trong console

pry(main)> show-routes -G login
  login GET    /login(.:format)       sessions#index

rất nhanh gọn.

Trên đây là một vài công cụ hữu ích mà pry cung cấp, với những công cụ này, việc đọc code sẽ trở nên dễ dàng hơn bao giờ hết.

0