Computer >> Máy Tính >  >> Lập trình >> Ruby

Rails 5, bổ sung trước mô-đun # và phần cuối của `Alias_method_chain`

Thông báo về Rails 4.2 có một số tin tức thú vị về Rails 5 sắp tới:Nó có thể sẽ yêu cầu Ruby 2.2. Điều này sẽ khiến nó trở thành phiên bản Rails đầu tiên để tận dụng tất cả những thứ tốt đẹp từ Ruby 2.

Bài đăng đã đề cập đến các ký hiệu thu thập rác và các đối số từ khóa. Nhưng đối với tôi, một trong những tính năng thú vị nhất của Ruby 2 là Module # prepend.

Tính tốt (và xấu) của alias_method_chain

Khi tôi lần đầu tiên học Rails, alias_method_chain hoàn toàn thổi bay tâm trí của tôi. Nó thực sự cho thấy Ruby có thể linh hoạt như thế nào.

Chỉ với một dòng mã, bạn có thể thay đổi hoàn toàn cách thức hoạt động của một phương pháp. Bạn không cần phải hack xung quanh các thư viện để thêm mã bạn muốn, bạn có thể thêm nó một cách nhanh chóng. alias_method_chain đã dẫn đến các bản vá lỗi đầu tiên của tôi cho đá quý, dẫn đến các yêu cầu kéo đầu tiên của tôi, dẫn đến các đóng góp nguồn mở đầu tiên của tôi.

Nhưng cũng giống như cách vá khỉ, alias_method_chain đã được sử dụng quá mức và các vấn đề của nó bắt đầu trở nên rõ ràng:

  • Tên phương thức mà nó tạo ra rất khó hiểu, điều này làm cho lỗi khó tìm và gỡ lỗi. Ví dụ:
class Person
  def greeting
    "Hello"
  end
end

module GreetingWithExcitement
  def self.included(base)
    base.class_eval do
      alias_method_chain :greeting, :excitement
    end
  end

  def greeting_with_excitement
    "#{greeting_without_excitement}!!!!!"
  end
end

Person.send(:include, GreetingWithExcitement)

Nếu bạn gặp lỗi trong Person#greeting , backtrace sẽ cho bạn biết rằng một lỗi thực sự đã xảy ra trong Person#greeting_without_excitement . Nhưng phương pháp đó thậm chí còn được định nghĩa ở đâu? Tôi không thấy nó ở đâu cả. Làm cách nào để bạn biết greeting nào phương pháp có lỗi trong đó không? Và các tên phương thức càng trở nên khó hiểu hơn khi bạn xâu chuỗi lại.

  • Nếu bạn gọi alias_method_chain hai lần với cùng một tham số trên cùng một lớp, bạn có thể gây ra tràn ngăn xếp. (Bạn có thể hiểu tại sao không?) Điều này thường không xảy ra, miễn là require của bạn tuyên bố nhất quán về những đường dẫn mà họ sử dụng. Nhưng sẽ rất khó chịu nếu bạn thường xuyên dán mã vào bảng điều khiển Rails.

  • Và phần còn lại của những điều được mô tả bởi bài đăng trên blog của Yehuda Katz. Bài đăng này đã thuyết phục rất nhiều nhà phát triển Rails bắt đầu từ bỏ alias_method_chain ủng hộ việc kế thừa mô-đun.

Vậy, tại sao nó vẫn được sử dụng?

Bạn có thể thay thế hầu hết alias_method_chain s bằng cách ghi đè các phương thức đó trong một mô-đun và đưa các mô-đun đó vào các lớp con của bạn. Nhưng điều đó chỉ hoạt động nếu bạn muốn ghi đè lớp cha của mình, không phải chính lớp của bạn. Đó là:

class ParentClass
  def log
    puts "In parent"
  end
end

class ChildClass < ParentClass
  def log
    puts "In child"
    super
  end

  def log_with_extra_message
    puts "In child, with extra message"
    log_without_extra_message
  end

  alias_method_chain :log, :extra_message
end

Nếu bạn đã chạy ChildClass.new.log , bạn sẽ thấy:

In child, with extra message
In child
In parent

nếu bạn cố gắng sử dụng mô-đun thay vì alias_method_chain , bạn có thể nhận được đầu ra là:

In child
In child, with extra message
In parent

Nhưng bạn không thể khớp với kết quả đầu ra ban đầu mà không thay đổi log trong ChildClass . Kế thừa Ruby không hoạt động theo cách đó. Chà, không.

Điều gì đã thay đổi trong Ruby 2.0?

Cho đến Ruby 2.0, không có cách nào để thêm mã bên dưới một lớp, chỉ ở trên nó. Nhưng với prepend , bạn có thể ghi đè một phương thức trong một lớp bằng một phương thức từ một mô-đun và vẫn truy cập vào phần triển khai của lớp với super . Vì vậy, bằng cách sử dụng ví dụ cuối cùng của chúng tôi, chúng tôi có thể nhận được đầu ra ban đầu với:

class ParentClass
  def log
    puts "In parent"
  end
end

module ExtraMessageLogging
  def log
    puts "In child, with extra message"
    super
  end
end

class ChildClass < ParentClass
  prepend ExtraMessageLogging
  def log
    puts "In child"
    super
  end
end
In child, with extra message
In child
In parent

Hoàn hảo.

Nếu prepend vẫn còn khó khăn trong đầu, hãy nghĩ về nó như làm một cái gì đó như thế này:

class NewChildClass < ChildClass
  include ExtraMessageLogging
end
  
ChildClass = NewChildClass

Ngoại trừ nó sẽ không gây nhầm lẫn với tên lớp của bạn và nó ảnh hưởng đến các đối tượng đã tồn tại.

(Có, bạn có thể gán lại tên lớp trong Ruby. Không, có lẽ đó không phải là một ý tưởng tuyệt vời.)

Điều này có ý nghĩa gì đối với Rails?

Vì vậy, lý do cuối cùng để sử dụng alias_method_chain không còn trong Ruby 2.0. Chúng tôi có thể lấy một trong số ít ví dụ còn lại về alias_method_chain trong Rails:

rails / activesupport / lib / active_support / core_ext / range / each.rb
require 'active_support/core_ext/module/aliasing'

class Range #:nodoc:

  def each_with_time_with_zone(&block)
    ensure_iteration_allowed
    each_without_time_with_zone(&block)
  end
  alias_method_chain :each, :time_with_zone

  def step_with_time_with_zone(n = 1, &block)
    ensure_iteration_allowed
    step_without_time_with_zone(n, &block)
  end
  alias_method_chain :step, :time_with_zone

  private
  def ensure_iteration_allowed
    if first.is_a?(Time)
      raise TypeError, "can't iterate from #{first.class}"
    end
  end
end

và thay thế nó bằng một mô-đun, thay vào đó:

require 'active_support/core_ext/module/aliasing'

module RangeWithTimeWithZoneSupport #:nodoc:

  def each(&block)
    ensure_iteration_allowed
    super(&block)
  end

  def step(n = 1, &block)
    ensure_iteration_allowed
    super(n, &block)
  end
  
  private
  def ensure_iteration_allowed
    if first.is_a?(Time)
      raise TypeError, "can't iterate from #{first.class}"
    end
  end
end

Range.send(:prepend, RangeSupportingTimeWithZone)

Nó rõ ràng hơn, Range#each không được đổi tên và ensure_iteration_allowed không phải là một con khỉ được vá.

Sử dụng kế thừa, không phải các bản vá

Ruby cung cấp cho bạn rất nhiều sự linh hoạt và đó là một trong những lý do tôi yêu thích nó. Nhưng nó cũng có một mô hình đối tượng mạnh mẽ. Vì vậy, khi bạn muốn đưa mã của riêng mình vào, hãy thử dựa vào các mô-đun và tính kế thừa trước khi bạn xâm nhập. Mã của bạn sẽ dễ hiểu và dễ gỡ lỗi hơn nhiều và bạn sẽ tránh được một số tác dụng phụ khó phát hiện của một cái gì đó như alias_method_chain .

alias_method_chain là một trong những phương pháp thú vị nhất mà tôi được giới thiệu trong Rails. Nhưng ngày của nó được đánh số. Chúng tôi đã phát triển nhanh hơn nó. Và tôi sẽ không bỏ lỡ nó khi nó biến mất.