Ruby modülünde bir örnek yöntemi eklemeden başlatabilir miyim?


181

Arka fon:

Birkaç örnek yöntemleri bildiren bir modül var

module UsefulThings
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end

Ve bu yöntemlerden bazılarını bir sınıftan çağırmak istiyorum. Normalde bunu ruby'de nasıl yaparsınız:

class UsefulWorker
  include UsefulThings

  def do_work
    format_text("abc")
    ...
  end
end

Sorun

include UsefulThingstüm yöntemleri getiriyor UsefulThings. Bu durumda sadece istiyorum format_textve açıkça istemiyorum get_fileve delete_file.

Bunun için birkaç olası çözüm görüyorum:

  1. Bir şekilde yöntemi herhangi bir yere dahil etmeden doğrudan modüle çağırınız
    • Bunun nasıl yapılacağını / yapıp yapamayacağını bilmiyorum. (Dolayısıyla bu soru)
  2. Bir şekilde dahil Usefulthingsve sadece bazı yöntemlerini getirmek
    • Bunun nasıl yapılacağını / yapıp yapamayacağını da bilmiyorum
  3. Bir proxy sınıfı oluşturun, buna ekleyin UsefulThings, ardından format_texto proxy örneğine temsilci seçin
    • Bu işe yarar, ancak anonim proxy sınıfları bir saldırıdır. Yuck.
  4. Modülü 2 veya daha fazla küçük modüle ayırın
    • Bu da işe yarayabilir ve muhtemelen aklıma gelen en iyi çözümdür, ancak düzinelerce ve düzinelerce modülün çoğalmasıyla sonuçlanacağımdan kaçınmayı tercih ederim - bunu yönetmek külfetli olurdu

Tek bir modülde neden pek çok ilgisiz fonksiyon var? Bu var ApplicationHelperbir herhangi bir şey başka bir yerde ait değil belirli yeterince için atık alanı olarak ekibimiz de-facto karar verdi uygulamayı, raylar gelen. Her yerde kullanılan çoğunlukla bağımsız yarar yöntemleri. Ayrı yardımcılara bölebilirdim, ama her biri 1 yöntemle 30 tane olurdu ... bu verimsiz görünüyor


4. yaklaşımı kullanırsanız (modülü bölerseniz), bir modülün diğerini tetiklemek için Module#includedgeri aramayı kullanarak her zaman otomatik olarak diğerini içermesini includesağlayabilirsiniz. Tek başına yararlı olduğu için format_textyöntem kendi modülüne taşınabilir. Bu, yönetimi biraz daha az külfetli yapar.
Batkins

Modül işlevlerinin yanıtlarındaki tüm referanslar tarafından şaşırdım. Varsayalım module UT; def add1; self+1; end; def add2; self+2; end; endve kullanmak istiyorsunuz add1ama add2sınıfta değil Fixnum. Bunun için modül işlevlerine sahip olmak nasıl yardımcı olur? Bir şey mi kaçırıyorum?
Cary Swoveland

Yanıtlar:


135

Bir modüldeki bir yöntem bir modül fonksiyonuna dönüştürülürse, bunu Mods olarak açıklanmış gibi modlardan çağırabilirsiniz.

module Mods
  def self.foo
     puts "Mods.foo(self)"
  end
end

Aşağıdaki module_function yaklaşımı, tüm Modları içeren sınıfları kırmayı önleyecektir.

module Mods
  def foo
    puts "Mods.foo"
  end
end

class Includer
  include Mods
end

Includer.new.foo

Mods.module_eval do
  module_function(:foo)
  public :foo
end

Includer.new.foo # this would break without public :foo above

class Thing
  def bar
    Mods.foo
  end
end

Thing.new.bar  

Ancak, neden bir takım ilgisiz fonksiyonların hepsinin aynı modülde ilk başta yer aldığını merak ediyorum?

Şundanpublic :foo sonra çağrılırsa hala çalışmayı içeren gösterilecek şekilde düzenlenmiştirmodule_function :foo


1
Bir kenara, module_functionyöntemi özel bir kod haline getirir, bu da diğer kodu kırar - aksi takdirde bu kabul edilen cevap olacaktır
Orion Edwards

Sonunda iyi bir şey yaptım ve kodumu ayrı modüllere yeniden düzenledim. Düşündüğüm kadar kötü değildi. Cevabınız hala en doğru şekilde çözecektir, orijinal kısıtlamalarımı WRT, bu yüzden kabul edin!
Orion Edwards

@dgtized ile ilgili işlevler her zaman tek bir modülde olabilir, bu da ad alanımı hepsiyle kirletmek istediğim anlamına gelmez. A Files.truncateve a varsa Strings.truncateve her ikisini de aynı sınıfta, açıkça kullanmak istiyorsanız, basit bir örnek . Belirli bir yönteme ihtiyaç duyduğum her seferinde yeni bir sınıf / örnek oluşturmak veya orijinali değiştirmek hoş bir yaklaşım değil, ama ben Ruby dev değilim.
TWiStErRob

146

Sadece mevcut aramaları (mevcut modülleri değiştirmeden veya yenilerini oluşturmadan) atmanın en kısa yolunun aşağıdaki gibi olacağını düşünüyorum:

Class.new.extend(UsefulThings).get_file

2
Erb dosyaları için çok yararlı. html.erb veya js.erb. Teşekkürler ! Merak ediyorum bu sistem hafızayı boşa harcıyor mu
Albert Català

5
@ AlbertCatalà benim testleri ve stackoverflow.com/a/23645285/54247 göre anonim sınıflar her şey gibi toplanan çöp, bu yüzden bellek israf etmemelidir.
dolzenko

1
Proxy olarak anonim bir sınıf yapmak istemiyorsanız, yöntem için proxy olarak bir nesneyi de kullanabilirsiniz. Object.new.extend(UsefulThings).get_file
3limin4t0r

83

Modüle "sahipseniz" bunu yapmanın başka bir yolu da kullanmaktır module_function.

module UsefulThings
  def a
    puts "aaay"
  end
  module_function :a

  def b
    puts "beee"
  end
end

def test
  UsefulThings.a
  UsefulThings.b # Fails!  Not a module method
end

test

27
Ve sahibi olmadığınız durumda: UsefulThings.send: module_function,: b
Dustin

3
module_function yöntemi özel bir tanesine dönüştürür (yine de benim IRB'imde de yapar), bu da diğer arayanları kırar :-(
Orion Edwards

Aslında bu yaklaşımı seviyorum, en azından benim için. Şimdi ModuleName.method :method_namebir yöntem nesnesi almak ve onu aramak için arayabiliriz method_obj.call. Aksi takdirde, yöntemi, orijinal nesne bir Modül ise mümkün olmayan orijinal nesnenin bir örneğine bağlamak zorunda kalacağım. Orion Edwards'a yanıt olarak module_function, orijinal örnek yöntemini özel yapar. ruby-doc.org/core/classes/Module.html#M001642
John

2
Orion - Bunun doğru olduğuna inanmıyorum. Ruby-doc.org/docs/ProgrammingRuby/html/… 'a göre, module_function "belirtilen yöntemler için modül işlevleri oluşturur. Bu işlevler modülle birlikte bir alıcı olarak çağrılabilir ve aynı zamanda Modül işlevleri orijinalin kopyalarıdır ve bu nedenle bağımsız olarak değiştirilebilir. Örnek yöntemi sürümleri özel yapılır. Bağımsız değişken olmadan kullanılırsa, daha sonra tanımlanan yöntemler modül işlevleri haline gelir. "
Ryan Crispin Heneise

2
ayrıca şu şekilde de tanımlayabilirsinizdef self.a; puts 'aaay'; end
Tilo

17

Bu yöntemleri başka bir sınıfa modül eklemeden çağırmak istiyorsanız, bunları modül yöntemleri olarak tanımlamanız gerekir:

module UsefulThings
  def self.get_file; ...
  def self.delete_file; ...

  def self.format_text(x); ...
end

ve sonra onları

UsefulThings.format_text("xxx")

veya

UsefulThings::format_text("xxx")

Her neyse, sadece ilgili yöntemleri bir modüle veya bir sınıfa koymanızı tavsiye ederim. Modülden sadece bir yöntem eklemek istediğiniz bir sorun varsa, o zaman kötü bir kod kokusu gibi geliyor ve ilgisiz yöntemleri bir araya getirmek iyi Ruby tarzı değil.


17

Modülü dahil etmeden (ve ara nesneler oluşturmadan) bir modül örneği yöntemini çağırmak için:

class UsefulWorker
  def do_work
    UsefulThings.instance_method(:format_text).bind(self).call("abc")
    ...
  end
end

Bu yaklaşıma dikkat edin: Kendine bağlanma yöntemi beklediği her şeyi sağlamayabilir. Örneğin, belki de format_textmodül tarafından sağlanan (genellikle) bulunmayan başka bir yöntemin var olduğunu varsayar.
Nathan

Modül destek yöntemi doğrudan yüklenebiliyorsa, herhangi bir modülü yükleyebilir. Modül seviyesinde değiştirmek daha iyidir. Ama bazı durumlarda, Bu satır isteyenler almak istiyorum.
twindai

6

İlk olarak, modülü ihtiyacınız olan faydalı şeylere ayırmanızı tavsiye ederim. Ancak her zaman çağrınız için bunu genişleten bir sınıf oluşturabilirsiniz:

module UsefulThings
  def a
    puts "aaay"
  end
  def b
    puts "beee"
  end
end

def test
  ob = Class.new.send(:include, UsefulThings).new
  ob.a
end

test

4

C. Bunları her zaman "nitelikli", bağımsız bir şekilde (UsefulThings.get_file) olarak çağırmak istiyorsanız, diğerlerinin işaret ettiği gibi sabit hale getirin,

module UsefulThings
  def self.get_file; ...
  def self.delete_file; ...

  def self.format_text(x); ...

  # Or.. make all of the "static"
  class << self
     def write_file; ...
     def commit_file; ...
  end

end

B.Karşılama yaklaşımını hala aynı durumlarda ve tek seferlik bağımsız çağırmada tutmak istiyorsanız, mixin ile kendini genişleten tek katmanlı bir modülünüz olabilir :

module UsefulThingsMixin
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end

module UsefulThings
  extend UsefulThingsMixin
end

Yani her ikisi de çalışır:

  UsefulThings.get_file()       # one off

   class MyUser
      include UsefulThingsMixin  
      def f
         format_text             # all useful things available directly
      end
   end 

IMHO module_function, her bir yönteme göre daha temiz - hepsini istemeniz durumunda.


extend selfortak bir deyimdir.
smathy

4

Soruyu anladığım gibi, bir modülün örnek yöntemlerinden bazılarını bir sınıfa karıştırmak istiyorsunuz.

Modül # içerme işlevinin nasıl çalıştığını düşünerek başlayalım . UsefulThingsİki örnekleme yöntemi içeren bir modülümüz olduğunu varsayın :

module UsefulThings
  def add1
    self + 1
  end
  def add3
    self + 3
  end
end

UsefulThings.instance_methods
  #=> [:add1, :add3]

ve Fixnum includebu modül:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  include UsefulThings
end

Görüyoruz ki:

Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" }
  #=> [:add2, :add3, :add1] 
1.add1
2 
1.add2
cat
1.add3
dog

UsefulThings#add3Geçersiz kılmayı bekliyordunuz Fixnum#add3, böylece 1.add3geri dönecek 4mi? Bunu düşün:

Fixnum.ancestors
  #=> [Fixnum, UsefulThings, Integer, Numeric, Comparable,
  #    Object, Kernel, BasicObject] 

Sınıf includemodül olduğunda, modül sınıfın üst sınıfı olur. Bu nedenle, mirasın nasıl çalıştığı nedeniyle, add3bir örneğine göndermek çağrılmaya ve geri dönmeye Fixnumneden olacaktır .Fixnum#add3dog

Şimdi bir yöntem ekleyelim :add2için UsefulThings:

module UsefulThings
  def add1
    self + 1
  end
  def add2
    self + 2
  end
  def add3
    self + 3
  end
end

Artık isteyen Fixnumiçin includesadece yöntemlerle add1ve add3. Böyle yapıyorsa, yukarıdakiyle aynı sonuçları almayı umuyoruz.

Yukarıdaki gibi yürüttüğümüzü varsayalım:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  include UsefulThings
end

Sonuç nedir? İstenmeyen bir yöntem :add2eklenir Fixnum, :add1yukarıda izah edilen nedenlerden ötürü, ilave edilir ve :add3ilave edilmez. Yani tek yapmamız gereken undef :add2. Bunu basit bir yardımcı yöntemle yapabiliriz:

module Helpers
  def self.include_some(mod, klass, *args)
    klass.send(:include, mod)
    (mod.instance_methods - args - klass.instance_methods).each do |m|
      klass.send(:undef_method, m)
    end
  end
end

ki böyle çağırıyoruz:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  Helpers.include_some(UsefulThings, self, :add1, :add3)
end

Sonra:

Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" }
  #=> [:add2, :add3, :add1] 
1.add1
2 
1.add2
cat
1.add3
dog

istediğimiz sonuç bu.


2

Birinin hala 10 yıl sonra ihtiyacı olup olmadığından emin değilim ama eigenclass kullanarak çözdüm.

module UsefulThings
  def useful_thing_1
    "thing_1"
  end

  class << self
    include UsefulThings
  end
end

class A
  include UsefulThings
end

class B
  extend UsefulThings
end

UsefulThings.useful_thing_1 # => "thing_1"
A.new.useful_thing_1 # => "thing_1"
B.useful_thing_1 # => "thing_1"

0

Neredeyse 9 yıl sonra genel bir çözüm:

module CreateModuleFunctions
  def self.included(base)
    base.instance_methods.each do |method|
      base.module_eval do
        module_function(method)
        public(method)
      end
    end
  end
end

RSpec.describe CreateModuleFunctions do
  context "when included into a Module" do
    it "makes the Module's methods invokable via the Module" do
      module ModuleIncluded
        def instance_method_1;end
        def instance_method_2;end

        include CreateModuleFunctions
      end

      expect { ModuleIncluded.instance_method_1 }.to_not raise_error
    end
  end
end

Uygulamanız gereken talihsiz numara, yöntemler tanımlandıktan sonra modülü dahil etmektir. Alternatif olarak, bağlam olarak tanımlandıktan sonra da ekleyebilirsiniz ModuleIncluded.send(:include, CreateModuleFunctions).

Ya da refleks_utils taşını kullanarak kullanabilirsiniz .

spec.add_dependency "reflection_utils", ">= 0.3.0"

require 'reflection_utils'
include ReflectionUtils::CreateModuleFunctions

Burada görebildiğimiz yanıtların çoğunluğu gibi yaklaşımınız orijinal sorunu ele almıyor ve tüm yöntemleri yüklüyor. Tek iyi yanıt, yöntemi orijinal modülden ayırmak ve hedeflenen sınıfta bağlamaktır, çünkü @renier zaten 3 yıl önce yanıt verir.
Joel AZEMAR

@JoelAZEMAR Sanırım bu çözümü yanlış anlıyorsunuz. Kullanmak istediğiniz modüle eklenecektir. Sonuç olarak, bunları kullanmak için hiçbir yönteminin dahil edilmesi gerekmeyecektir. OP tarafından olası çözümlerden biri olarak önerildiği gibi: "1, Bir şekilde yöntemi herhangi bir yere dahil etmeden doğrudan modüle çağırınız". Bu nasıl çalışır.
thisismydesign
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.