2008-08-14 25 views
34

Ok, vì vậy tôi đã tái cấu trúc mã của mình trong ứng dụng Rails nhỏ trong một nỗ lực để loại bỏ trùng lặp, và nói chung làm cho cuộc sống của tôi dễ dàng hơn (như tôi thích một cuộc sống dễ dàng). Một phần của việc tái cấu trúc này, đã di chuyển mã phổ biến cho hai mô hình của tôi thành một mô-đun mà tôi có thể bao gồm nơi tôi cần nó.Ruby mixins và gọi siêu phương pháp

Cho đến nay, rất tốt. Có vẻ như nó sẽ thành công, nhưng tôi vừa mới gặp một vấn đề mà tôi không chắc chắn làm thế nào để có được xung quanh. Mô-đun (mà tôi đã gọi là có thể gửi), sẽ chỉ là mã xử lý fax, gửi thư điện tử hoặc in một tệp PDF của tài liệu. Vì vậy, ví dụ, tôi có một đơn đặt hàng, và tôi có Đơn đặt hàng nội bộ (được viết tắt là tưởng tượng theo ISO).

Vấn đề tôi đã xảy ra, là tôi muốn một số biến được khởi tạo (được khởi tạo cho những người không đánh vần đúng: P) sau khi đối tượng được tải, vì vậy tôi đã sử dụng móc after_initialize. Không vấn đề gì ... cho đến khi tôi bắt đầu thêm một số mixin khác.

Vấn đề tôi có, là tôi có thể có một after_initialize trong bất kỳ một trong mixins của tôi, vì vậy tôi cần phải bao gồm một siêu cuộc gọi vào lúc bắt đầu để đảm bảo mixin khác after_initialize cuộc gọi được gọi . Đó là tuyệt vời, cho đến khi tôi kết thúc gọi điện thoại siêu và tôi nhận được một lỗi vì không có siêu để gọi.

Dưới đây là một ví dụ nhỏ, trong trường hợp tôi đã không được lộn xộn đủ:

class Iso < ActiveRecord::Base 
    include Shared::TracksSerialNumberExtension 
    include Shared::OrderLines 
    extend Shared::Filtered 
    include Sendable::Model 

    validates_presence_of :customer 
    validates_associated :lines 

    owned_by    :customer 
    order_lines    :despatched # Mixin 

    tracks_serial_numbers :items # Mixin 

    sendable :customer      # Mixin 

    attr_accessor :address 

    def initialize(params = nil) 
    super 
    self.created_at ||= Time.now.to_date 
    end 
end 

Vì vậy, nếu mỗi một trong những mixins có một cuộc gọi after_initialize, với một siêu cuộc gọi, làm thế nào tôi có thể ngăn chặn rằng cuộc gọi siêu mới nhất từ ​​việc tăng lỗi? Làm thế nào tôi có thể kiểm tra rằng phương pháp siêu tồn tại trước khi tôi gọi nó?

Trả lời

40

Bạn có thể sử dụng này:

super if defined?(super) 

Dưới đây là một ví dụ:

class A 
end 

class B < A 
    def t 
    super if defined?(super) 
    puts "Hi from B" 
    end 
end 

B.new.t 
0

Thay vì kiểm tra nếu các phương pháp siêu tồn tại, bạn chỉ có thể định nghĩa nó

class ActiveRecord::Base 
    def after_initialize 
    end 
end 

này hoạt động trong thử nghiệm của tôi, và không nên phá vỡ bất kỳ mã hiện tại của bạn, bởi vì tất cả các lớp khác của bạn mà định nghĩa nó sẽ chỉ lặng lẽ ghi đè phương pháp này anyway

+1

downvoted bởi vì nó không phải là một giải pháp chung. Vấn đề là bạn không biết liệu super có tồn tại hay không, do đó, hãy bắt cóc ActiveRecord :: Base # after_initialize vào sự tồn tại, bạn tạo một điểm mà mã của bạn sẽ bị ngắt nếu/khi ActiveRecord thêm Base # after_initialize, hoặc arity của nó bị thay đổi ; nó là beter xa để có điều kiện gọi nó nếu nó được xác định. – yaauie

+0

@yaauie - chắc chắn, tôi có thể đặt một 'nâng cao 'oh không' nếu phương pháp.bao gồm? (: After_initialize)' trước khỉ con, nhưng điều đó sẽ làm cho ví dụ khó hiểu hơn ... Thật dễ dàng để bắt kịp trong chi tiết tất cả các trường hợp cạnh mà bài học thực tế ở đây (chỉ cần vá một phương pháp cơ bản trong) sẽ bị lạc trong tiếng ồn. –

+1

monkeypatching không phải là một giải pháp chung và không nên - nói chung - được khuyến khích. Một câu trả lời liên quan đến một monekypatch một lần mà * có thể * xảy ra để làm việc dẫn đến mã phức tạp và không cần thiết, đặc biệt là nếu bạn không có quyền kiểm soát tất cả các cách lên chuỗi thừa kế. – yaauie

3

Bạn đã thử alias_method_chain chưa? Về cơ bản, bạn có thể xích tất cả các cuộc gọi after_initialize của mình. Nó hoạt động như một trình trang trí: mỗi phương thức mới thêm một lớp chức năng mới và chuyển điều khiển lên phương thức "ghi đè" để thực hiện phần còn lại.

3

Các bao gồm lớp (điều được thừa kế từ ActiveRecord::Base, trong đó, trong trường hợp này là Iso) thể định nghĩa riêng của mình after_initialize, vì vậy giải pháp nào khác hơn alias_method_chain (hoặc các răng cưa mà tiết kiệm bản gốc) có nguy cơ ghi đè mã. Giải pháp @Orion Edwards 'là giải pháp tốt nhất mà tôi có thể đưa ra. Có những người khác, nhưng họ là đến nay nhiều kẻ tấn công hơn.

alias_method_chain cũng có lợi ích khi tạo các phiên bản được đặt tên của phương thức after_initialize, có nghĩa là bạn có thể tùy chỉnh thứ tự cuộc gọi trong những trường hợp hiếm hoi quan trọng. Nếu không, bạn đang ở lòng thương xót của bất cứ thứ tự bao gồm cả các lớp bao gồm mixins.

sau:

tôi đã đăng một câu hỏi cho ruby-on-ray-core mailing list về việc tạo mặc định triển khai sản phẩm nào của tất cả các callbacks. Tuy nhiên, quy trình tiết kiệm sẽ kiểm tra tất cả chúng, vì vậy tôi không thấy lý do tại sao chúng không nên ở đó. Nhược điểm duy nhất là tạo thêm khung xếp trống, nhưng đó là khá rẻ trên mọi triển khai đã biết.

2

Bạn chỉ có thể ném một cách nhanh chóng có điều kiện trong đó:

super if respond_to?('super') 

và bạn nên sử dụng tốt - không có phương pháp vô dụng thêm; Đẹp và sạch sẽ.

+0

Điều này có ** không ** xuất hiện để hoạt động. respond_to? ('super') sẽ luôn trả về false. – averell