2011-01-11 11 views
30

Ilekroć próbuję rozszerzyć moduł ruby, tracę metody modułu. Nie uwzględni tego ani nie rozszerzy. Rozważmy fragment:Rozszerzanie modułu Ruby w innym module, łącznie z metodami modułu

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

module B 
    include A 
end 

B.say_hi #undefined_method 

czy B zawiera lub rozszerza, say_hi nie zostaną zdefiniowane.

Czy istnieje sposób na osiągnięcie czegoś takiego?

+1

Czy możesz podać konkretny przypadek, w którym chciałbyś to zrobić? Dziedziczenie modułów jest tym, czego naprawdę chcesz, a to jest specjalnie i celowo obsługiwane tylko dla klas w Ruby. Dlaczego nie: a) zawsze uwzględniać B i A w tym samym obiekcie, lub b) gdy B zawiera również A? – Phrogz

+0

Powód, dla którego chcę to, jest trochę skomplikowany. Ten moduł służy do tworzenia wielu klas ActiveRecord :: Base z dokumentu XML opisującego tabele i relacje DB. Chcę móc to zrobić kilka razy. Kod modułu jest taki sam dla każdego, ale aby przestrzenie nazw i powiązane klasy nie kolidowały, muszą być odrębnymi modułami. Nie chcę też tworzyć instancji klasy, aby włączyć moduł za każdym razem, gdy to robię, wykluczając i stosując metody instancji. –

Odpowiedz

9

Nie sądzę, że jest jakiś prosty sposób na zrobienie tego.

Więc tutaj jest złożonym sposobem:

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

Można umieścić go w metodzie pomocnika tak:

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

Genialny! Zajęło mi zawsze, aby dowiedzieć się, jak obejść ten błąd: 'TypeError: metoda singleton wywołała inny obiekt'. '# to_proc' rozwiązało to z łatwością. –

24

Jeśli jesteś autorem module A i często potrzebują tego, ty może ponownie autorowi tak:

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

Próbowałem tego i to nie działa w moim przypadku. Jak wspomniałem w moim komentarzu (myślę po twojej odpowiedzi) celem tego jest użycie funkcji, która tworzy klasy w przestrzeni modułu używając const_set i Class.new ... Ale! Jeśli robię to w ten sposób i przywołuję klasy B.create, stwierdzam, że klasy nie są zdefiniowane tylko w B, ale również w A. –

+0

@ JonathanMartin Musisz zaktualizować swoje pytanie odtwarzalnym test case. Może ActiveRecord robi szaloną magię i spamuje twoje przestrzenie, ale powinno po prostu działać. – Phrogz

+0

To jest fajne. Thanx. –

3

Zastosowanie 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

Powiedziałeś, że klejnot c ext działa tylko z rubinem 1.9.2. =) – puchu

+0

@puchu spójrz na stan, w którym zostawiłem tę odpowiedź - 2011 – horseyguy

+0

16 lipca 2015 r. I Twój klejnot wciąż nie działa dzisiaj z Ruby 2.2 i 2.3. – puchu

2

Johnathan, Nie jestem pewien, jeśli nadal zastanawiasz się o to, ale istnieją dwa różne sposoby korzystania z modułów w Ruby. A.) używasz modułów w ich samodzielnej formie Base :: Tree.entity (params) bezpośrednio w kodzie lub B.) używasz modułów jako mixin lub metod pomocniczych.

A. Umożliwi wykorzystanie modułów jako wzorca przestrzeni nazw. To jest dobre dla dużych projektów, w których jest szansa dla nazwy metody konflikty

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

Teraz można to wykorzystać, aby stworzyć jakąś strukturę drzewa w kodzie, bez konieczności wystąpienia nową klasę dla każdego połączenia do bazy :: Tree.entity.

Innym sposobem wykonywania przestrzeni nazw jest podział na klasy.

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 

Teraz mamy potencjał nakładania się w naszych klasach. Chcemy wiedzieć, że kiedy tworzymy klasę aktorów, że jest to właściwa klasa, to właśnie tutaj przydatne są przestrzenie nazw.

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

B. Mix-Ins To są twoi przyjaciele. Używaj ich, gdy myślisz, że będziesz musiał zrobić coś więcej niż jeden raz w swoim kodzie.

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 

Teraz, kiedy trzeba formatować xmlstring jako hash lub usunąć końcowe spacje w którymkolwiek z przyszłych kodu, po prostu zmieszać go w.

module Fun 
    class Ruby 
    include Friendly::Formatter 

    attr_accessor :string 

    def initialize(params={}) 
    end 

    end 
end 

Teraz można sformatować ciąg w swojej klasa.

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) 

Mam nadzieję, że jest to wystarczająco dobre wytłumaczenie.Powyższe punkty są dobrym przykładem sposobów rozszerzania klas, ale w przypadku modułów najtrudniejsze jest zastosowanie słowa kluczowego self. Odnosi się do zakresu obiektu w hierarchii obiektu ruby. Jeśli więc chcesz użyć modułu jako miksu i nie chcesz deklarować niczego pojedynczego, nie używaj własnego słowa kluczowego, ale jeśli chcesz zachować stan w obiekcie, po prostu użyj klasy i wymieszaj- w modułach, które chcesz.

0

Nie podoba mi się każdy, kto używa self.included. Mam prostsze rozwiązanie:

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 
Powiązane problemy