2011-01-11 24 views
30

Bất cứ khi nào tôi cố gắng mở rộng mô-đun ruby, tôi sẽ mất các phương thức mô-đun. Không bao gồm cũng không mở rộng sẽ làm điều này. Xem xét đoạn mã:Mở rộng mô-đun Ruby trong mô-đun khác, bao gồm các phương thức mô-đun

module A 
    def self.say_hi 
    puts "hi" 
    end 
end 

module B 
    include A 
end 

B.say_hi #undefined_method 

Cho dù B bao gồm hoặc mở rộng A, say_hi sẽ không được xác định.

Có cách nào để thực hiện điều gì đó như thế này không?

+1

Bạn có thể cung cấp trường hợp cụ thể nơi bạn muốn thực hiện việc này không? Thừa kế mô-đun là những gì bạn thực sự muốn, và điều này đặc biệt và cố ý chỉ được hỗ trợ cho Lớp học trong Ruby. Tại sao không phải là a) luôn luôn bao gồm B và A trong cùng một đối tượng, hoặc b) khi bao gồm B cũng bao gồm A? – Phrogz

+0

Lý do tôi muốn điều này là một chút phức tạp. Mô-đun được đề cập được sử dụng để tạo nhiều lớp ActiveRecord :: Base từ một tài liệu XML mô tả các bảng và mối quan hệ DB. Tôi muốn có thể làm điều này vài lần khác nhau. Mã mô-đun là như nhau cho mỗi một, nhưng để các không gian tên và các lớp liên quan không xung đột, chúng cần phải là các mô-đun riêng biệt. Tôi cũng không muốn phải khởi tạo một lớp chỉ để bao gồm các mô-đun mỗi khi tôi làm điều này, loại trừ và các phương thức dụ. –

Trả lời

9

Tôi không nghĩ có cách nào đơn giản để làm điều đó.

Vì vậy, đây là một cách phức tạp:

module B 
    class << self 
    A.singleton_methods.each do |m| 
     define_method m, A.method(m).to_proc 
    end 
    end 
end 

Bạn có thể đặt nó trong một phương pháp helper như thế này:

class Module 
    def include_module_methods(mod) 
    mod.singleton_methods.each do |m| 
     (class << self; self; end).send :define_method, m, mod.method(m).to_proc 
    end 
    end 
end 

module B 
    include_module_methods A 
end 
+1

Brilliant! Nó đã cho tôi mãi mãi để tìm ra cách để làm việc xung quanh lỗi này: 'TypeError: singleton phương pháp được gọi là cho một đối tượng khác nhau'. '# to_proc' đã giải quyết vấn đề này một cách dễ dàng. –

24

Nếu bạn là tác giả của module A và sẽ thường xuyên cần điều này, bạn có thể tái tác giả A như vậy:

module A 
    module ClassMethods 
    def say_hi 
     puts "hi" 
    end 
    end 
    extend ClassMethods 
    def self.included(other) 
    other.extend(ClassMethods) 
    end 
end 

module B 
    include A 
end 

A.say_hi #=> "hi" 
B.say_hi #=> "hi" 
+0

Tôi đã thử điều này, và nó không hoạt động cho trường hợp của tôi. Như tôi đã đề cập trong bình luận của tôi (tôi nghĩ sau câu trả lời của bạn) mục đích của việc này là sử dụng một hàm tạo các lớp trong không gian mô-đun bằng cách sử dụng const_set và Class.new ... Nhưng! Nếu tôi làm theo cách này và gọi B.create_classes, tôi thấy rằng các lớp không chỉ được định nghĩa trong B, mà còn trong A. –

+0

@JonathanMartin Bạn sẽ cần phải cập nhật câu hỏi của mình với trường hợp kiểm tra có thể tái tạo. Có lẽ ActiveRecord đang làm một số phép thuật điên rồ và gửi spam các không gian của bạn, nhưng nó chỉ nên hoạt động. – Phrogz

+0

Điều này thật tuyệt. Thanx. –

3

Sử dụng include_complete

gem install include_complete

module A 
    def self.say_hi 
    puts "hi" 
    end 
end 

module B 
    include_complete A 
end 

B.say_hi #=> "hi" 
+0

Bạn nói rằng c ext của gem chỉ hoạt động với ruby ​​1.9.2. =) – puchu

+0

@puchu nhìn vào trạng thái tôi đã để lại câu trả lời này - 2011 – horseyguy

+0

ngày 16 tháng 7 năm 2015. Và đá quý của bạn vẫn không hoạt động với ruby ​​2.2 và 2.3 hôm nay. – puchu

2

Johnathan, Tôi không chắc chắn nếu bạn vẫn đang băn khoăn về vấn đề này, nhưng có hai cách khác nhau để sử dụng module trong ruby. A.) bạn sử dụng các mô-đun dưới dạng tự chứa Base :: Tree.entity (params) trực tiếp trong mã của bạn, hoặc B.) bạn sử dụng các mô-đun như các phương thức mixin hoặc helper.

A. Sẽ cho phép bạn sử dụng mô-đun làm mẫu Không gian tên. Điều này là tốt cho các dự án lớn, nơi có là cơ hội cho tên phương pháp xung đột

module Base 
    module Tree 
    def self.entity(params={},&block) 
     # some great code goes here 
    end 
    end 
end 

Bây giờ bạn có thể sử dụng để tạo ra một số loại cấu trúc cây trong mã của bạn, mà không cần phải khởi tạo một lớp mới cho mỗi cuộc gọi đến cơ sở :: Tree.entity.

Một cách khác để thực hiện Không gian tên là trên lớp theo cơ sở lớp học.

module Session 
    module Live 
    class Actor 
     attr_accessor :type, :uuid, :name, :status 
     def initialize(params={},&block) 
     # check params, insert init values for vars..etc 
     # save your callback as a class variable, and use it sometime later 
     @block = block 
     end 

     def hit_rock_bottom 
     end 

     def has_hit_rock_bottom? 
     end 

     ... 
    end 
end 
class Actor 
    attr_accessor :id,:scope,:callback 
    def initialize(params={},&block) 
    self.callback = block if block_given? 
    end 

    def respond 
    if self.callback.is_a? Proc 
     # do some real crazy things... 
    end 
    end 
end 
end 

Bây giờ chúng tôi có khả năng chồng lấp trong các lớp học của chúng tôi. Chúng tôi muốn biết rằng khi chúng ta tạo ra một lớp Actor rằng đó là lớp chính xác, vì vậy đây là nơi không gian tên có ích.

Session::Live::Actor.new(params) do |res|... 
Session::Actor.new(params) 

B. Kết hợp Đây là bạn của bạn. Sử dụng chúng bất cứ khi nào bạn nghĩ rằng bạn sẽ phải làm điều gì đó nhiều hơn một lần trong mã của bạn.

module Friendly 
    module Formatter 
    def to_hash(xmlstring) 
     #parsing methods 
     return hash 
    end 

    def remove_trailing_whitespace(string,&block) 
     # remove trailing white space from that idiot who pasted from textmate 
    end 
    end 
end 

Bây giờ bất cứ khi nào bạn cần định dạng một xmlstring như một băm, hoặc loại bỏ dấu khoảng trắng trong bất kỳ mã tương lai của bạn, chỉ cần trộn nó trong.

module Fun 
    class Ruby 
    include Friendly::Formatter 

    attr_accessor :string 

    def initialize(params={}) 
    end 

    end 
end 

Bây giờ bạn có thể định dạng chuỗi trong của bạn lớp học.

fun_ruby = Fun::Ruby.new(params) 
fun_ruby.string = "<xml><why><do>most</do><people></people><use>this</use><it>sucks</it></why></xml>" 
fun_ruby_hash = fun_ruby.to_hash(fun_ruby.string) 

Hy vọng đây là giải thích đủ tốt.Các điểm nêu trên là ví dụ tốt về cách mở rộng các lớp học, nhưng với các mô-đun, phần khó khăn là khi sử dụng từ khóa tự. Nó đề cập đến phạm vi của đối tượng trong hệ thống phân cấp đối tượng của Ruby. Vì vậy, nếu bạn muốn sử dụng một mô-đun như là một hỗn hợp, và không muốn khai báo bất cứ điều gì singleton, không sử dụng từ khóa tự, tuy nhiên nếu bạn muốn giữ trạng thái bên trong đối tượng, chỉ cần sử dụng một lớp và trộn trong các mô-đun bạn muốn.

0

Tôi không thích mọi người sử dụng self.included. Tôi có giải pháp đơn giản hơn:

module A 
    module ClassMethods 
    def a 
     'a1' 
    end 
    end 
    def a 
    'a2' 
    end 
end 

module B 
    include A 

    module ClassMethods 
    include A::ClassMethods 
    def b 
     'b1' 
    end 
    end 
    def b 
    'b2' 
    end 
end 

class C 
    include B 
    extend B::ClassMethods 
end 

class D < C; end 

puts D.a 
puts D.b 
puts D.new.a 
puts D.new.b 
Các vấn đề liên quan