2011-01-19 60 views
5

Tôi đang cố gắng ghi đè phương thức được tạo động bằng cách bao gồm một mô-đun.Tại sao bao gồm mô-đun này không ghi đè lên phương pháp được tạo động?

Trong ví dụ bên dưới, liên kết Ripple thêm phương thức rows= vào Bảng. Tôi muốn gọi phương pháp đó, nhưng cũng làm một số công cụ bổ sung sau đó.

Tôi đã tạo mô-đun để ghi đè phương pháp, nghĩ rằng mô-đun row= sẽ có thể gọi super để sử dụng phương pháp hiện có.

class Table 

    # Ripple association - creates rows= method 
    many :rows, :class_name => Table::Row 

    # Hacky first attempt to use the dynamically-created 
    # method and also do additional stuff - I would actually 
    # move this code elsewhere if it worked 
    module RowNormalizer 
    def rows=(*args) 
     rows = super 
     rows.map!(&:normalize_prior_year) 
    end 
    end 
    include RowNormalizer 

end 

Tuy nhiên, tôi mới rows= không bao giờ được gọi là, được minh chứng bởi thực tế rằng nếu tôi tăng một ngoại lệ bên trong nó, không có gì xảy ra.

Tôi biết mô-đun đang được bao gồm, bởi vì nếu tôi đặt nó vào trong đó, ngoại lệ của tôi sẽ được nâng lên.

 included do 
     raise 'I got included, woo!' 
     end 

Ngoài ra, nếu thay vì rows=, module định nghĩa somethingelse=, phương pháp đó là callable.

Tại sao phương pháp mô-đun của tôi không ghi đè phương thức được tạo động?

Trả lời

10

Hãy làm một thí nghiệm:

class A; def x; 'hi' end end 
module B; def x; super + ' john' end end 
A.class_eval { include B } 

A.new.x 
=> "hi" # oops 

Tại sao vậy? Câu trả lời rất đơn giản:

A.ancestors 
=> [A, B, Object, Kernel, BasicObject] 

B là trước A trong chuỗi tổ tiên (bạn có thể nghĩ về điều này như BbênA). Do đó, A.x luôn được ưu tiên hơn B.x.

Tuy nhiên, điều này có thể được làm việc xung quanh:

class A 
    def x 
    'hi' 
    end 
end 

module B 
    # Define a method with a different name 
    def x_after 
    x_before + ' john' 
    end 

    # And set up aliases on the inclusion :) 
    # We can use `alias new_name old_name` 
    def self.included(klass) 
    klass.class_eval { 
     alias :x_before :x 
     alias :x :x_after 
    } 
    end 
end 

A.class_eval { include B } 

A.new.x #=> "hi john" 

Với ActiveSupport (và do đó Rails) bạn đã mô hình này thực hiện như alias_method_chain(target, feature)http://apidock.com/rails/Module/alias_method_chain:

module B 
    def self.included(base) 
    base.alias_method_chain :x, :feature 
    end 

    def x_with_feature 
    x_without_feature + " John" 
    end 
end 

Cập nhật Ruby 2 đi kèm với Module#prepend , mà không ghi đè lên các phương pháp của A, làm cho alias hack này không cần thiết cho hầu hết các trường hợp sử dụng.

+0

Tôi sắp sửa thăng hoa, nhưng sau đó bạn tắt và để mọi người bỏ cuộc. :-) –

+0

Cảm ơn! Tôi nên biết điều này: Tôi đã viết ra chuỗi thừa kế ở đây ... http://stackoverflow.com/questions/3492679/ruby-determining-method-origins :) –

2

Tại sao phương pháp mô-đun của tôi không ghi đè phương thức được tạo động?

Vì đó không phải là cách thức hoạt động của kế thừa. Các phương thức được định nghĩa trong một lớp ghi đè lên các lớp được kế thừa từ các lớp/mô-đun khác, không phải là cách khác xung quanh.

Trong Ruby 2.0, có Module#prepend, mà hoạt động giống như Module#include, ngoại trừ nó chèn các module như một lớp con thay vì một lớp cha trong chuỗi thừa kế.

+0

Tuyệt vời, tôi thực sự vui mừng khi thấy Module # prepend lên kế hoạch (ít nhất là dự kiến) cho Ruby 2 !! (Xem http://redmine.ruby-lang.org/issues/1102) –

0

Nếu bạn extend thể hiện của lớp học, bạn sẽ có thể làm điều đó.

class A 
    def initialize 
    extend(B) 
    end 
    def hi 
    'hi' 
    end 
end 
module B 
    def hi 
    super[0,1] + 'ello' 
    end 
end 

obj = A.new 
obj.hi #=> 'hello' 
Các vấn đề liên quan