Những điều thú vị đằng sau những mã code Ruby on Rails thường xuyên được sử dụng

Posted by : at

Category : Technical


Link bài viết gốc: Explaining magic behind popular Ruby on Rails code

Những điều thú vị đằng sau những mã code Ruby on Rails thường xuyên được sử dụng

Khám phá Ruby code đằng sau những giải pháp phổ biến để tìm hiểu cách chúng được tạo ra

Ngôn ngữ Ruby cho phép chúng ta dễ dàng tạo ra các DSL đẹp và cú pháp thiết kế thư viện mà bất cứ ai cũng có thể dễ dàng sử dụng bất chấp với kinh nghiệm lập trình. Code thường xuyên trông thật hoàn hảo, nhưng đội khi lại không rõ ràng là làm thế nào mà nó lại giải quyết được vấn đề đó. Trong bài này, tôi sẽ giải thích một vài giải pháp khá phổ biến được sử dụng ở tương đối nhiều gems, và bạn có thể dễ dàng sử dụng nó trong dự án của bạn một khi bạn hiểu cách có thể xây dựng được 1 đoạn code tương tự.

Bạn có thể thấy đoạn coe này ở hầu hết các Ruby gem được sử dụng để khởi tạo các biến cần thiết cho sử dụng sau này:

SomeGem.configure do |config|
  config.api_key = 'api_key'
  config.app_name = 'My App'
end

Bằng cách này các biến rất dễ dàng để đọc và mở rộng. Nó hoạt động tương tự các method về yields. Trong khi mỗi phương thức yields Chúng ta có thể xây dựng config bằng cách:

module SomeGem
  class << self
    attr_accessor :configuration

    def configure
      self.configuration ||= Configuration.new
      yield(configuration)
    end
  end

  class Configuration
    attr_accessor :api_key, :app_name
  end
end

Bây giờ hãy thử:

SomeGem.configure do |config|
  config.api_key = 'api_key'
  config.app_name = 'My App'
end

SomeGem.configuration.api_key # => 'api_key'
SomeGem.configuration.app_name # => 'My App'

Sau đó bạn có thể truy cập giá trị giống như bất cứ đối tượng nào khác.

Dynamic methods

Nếu bạn đã từng sử dụng Ruby on Rails, bạn có thể thấy các method đề cập tới các phần tử của model. Vì mã code không nhận biết được tên thuộc tính nếu bạn không định nghĩa chúng, nó sẽ tự động tạo một method, và bạn sẽ khong tìm thấy nơi đã định nghĩa chúng.

Cùng xem một số ví dụ assuming bạn có một class User và phần tử first_namelast_name đã được định nghĩa bên trong class User:

user = User.new(first_name: "John", last_name: "Doe")
user.first_name_is?('John') # => true
user.last_name_is?('John') # => false

Dĩ nhiên, bạn có thể gọi user.first_name == 'John', nhưng tôi đã quyết định cho bạn thấy straightforward trường hợp cho demonstration purposes. Đây là các định nghĩa class của chúng ta:

class User
  attr_reader :first_name, :last_name

  def initialize(first_name:, last_name:)
    @first_name = first_name
    @last_name = last_name
  end
end

Bây giờ, khi chúng ta gọi #first_name_is? hoặc last_name_is_mike? chúng ta sẽ nhận được lỗi NoMethodError bởi vĩ chúng ta đã không định nghĩa các methods đó. Lỗi này là điểm bắt đầu khi implementtation phương pháp động. Ruby exposesphương thức method_missing cho phép chúng ta làm nhiều việc với một method không tồn tại và từ chối nếu cúng ta muốn in ra lỗi:

class User
  attr_reader :first_name, :last_name

  def initialize(first_name:, last_name:)
    @first_name = first_name
    @last_name = last_name
  end

  private

  def method_missing(method_name, *args, &block)
    puts "You are missing #{method_name} method"
    super # raise the error anyway
  end
end

Sau khi chạy đoạn code c=trên, bạn có thể thấy đoạn text được in ra bởi error:

You are missing first_name_is? method

Phương thức method_missing cho phép 3 tham số truyền vào:

method_name - tên của phương thức không tồn tại trước đó args - tham số optional, tham số được truyền vào trước đo block - tham số optional, khối được excuted bên trong phương thức

Nếu chúng ta muốn tạo một phương thức động, đầu tiên chúng ta phải định nghĩa khi nào kết thúc với _is? và bắt đầu với một tên thuộc tính. Sau đó chúng ta sẽ trả về lỗi nếu tên method không thoả mãn hoặc được gọi bởi một phần tử không được định nghĩa hoặc so sánh giữa một giá trị phần tử với phần tử đầu tiên và trả về lỗi:

def method_missing(method_name, *args, &block)
  attribute_name = method_name.to_s.match(/(.*)_is\?/)&.captures&.first

  if !attribute_name.nil? && instance_variable_defined?("@#{attribute_name}")
    instance_variable_get("@#{attribute_name}") == args.first
  else
    super
  end
end

def respond_to_missing?(method_name, include_private = false)
  method_name.to_s.end_with?('_is?') || super
end

Bây giờ chúng ta có thể bắt đầu với class và xem cách nó hoạt động:

user = User.new(first_name: 'John', last_name: 'Doe')
user.first_name_is?('John') # => true
user.last_name_is?('Tom') # => false

user.method(:first_name_is?).call('John') # => true

# call on not existing attribute
user.age_is?('John') # => NoMethodError

Hãy ghi nhớ rằng luôn định nghĩa respond_to_missing? khi ghi đè method_missing

Nếu bạn muốn có nhiều phương thức phức tạp hơn, bạn có thể muốn tạo một định nghĩa phương thức động.

Duck typing

Một số đối tượng behave khác nhau khi bạn gọi to_s tới chúng (hoặc một số phương thức từ thư viện ngôn ngữ standard). Bạn có thể theo dõi ví dụ:

response = Request.get('some_url')

puts response # => "status: 200, body: some page body"
puts response.status # => 200
puts response.body # => "some page body"

Hãy tạo một định nghĩa ví dụ về lớp ResponseRequest

class Response
  attr_reader :status, :body

  def initialize(status:, body:)
    @status = status
    @body = body
  end
end

class Request
  def self.get(url)
    Response.new(status: 200, body: "some page body")
  end
end

Fine, but when calling the same code as before, we would not get the same result: Fine, nhưng khi gọi code giống như trước đó, bạn sẽ không lấy được kết quả giống nhau:

response = Request.get('some_url')

puts response # => #<Response:0x00007f9e3f1a0150>

It happens because each time you use puts, it calls the to_s method on the passed thing, and in our case, the default to_s method defined on an object is called. It returns the string that includes the class name.

Nó xảy ra bởi vì mỗi thời điểm bạn sử dụng lệnh puts, nó sẽ gọi phương thức to_s khi được truyền vào, và trong trường hợp này, phương thức mặc định to_s đã được định nghĩa và một đối tượng đã được gọi. Nó trả về đoạn string mà đã được includes vào trong class name.

Nếu chúng ta muốn thay đổi hành vi đó, chúng ta có thể định nghĩa phương thức to_s:

class Response
  attr_reader :status, :body

  def initialize(status:, body:)
    @status = status
    @body = body
  end

  def to_s
    "status: #{@status}, body: #{@body}"
  end
end

Bây giờ nó sẽ hoạt động giống như kì vọng:

response = Request.get('some_url')

puts response # => "status: 200, body: some page body"

If it quacks like a duck, it’s a duck. If it implements the to_s method, you can consider it as a string and use it as a string. Nếu tiếng kêu quacks giống như duck, nó sẽ là một con duck. Nếu nó implements phương thức to_s, bạn có thể consider nó giống như string và sử dụng nó như là một string.


About Hung Nguyen
Hung Nguyen

Hi I a Hung, a Web Developer and Project manager.

Email : hungnv950@gmail.com

Website : http://hungnv950.github.io

About Hung Nguyen

Hi, my name is Hung Nguyen

Star
Categories
Useful Links