Ruby'de yöntem argümanlarına erişmenin bir yolu var mı?


104

Ruby ve ROR'da yeni ve her gün onu seviyorum, işte benim sorum şu, çünkü onu nasıl google'layacağımı bilmiyorum (ve denedim :))

yöntemimiz var

def foo(first_name, last_name, age, sex, is_plumber)
    # some code
    # error happens here
    logger.error "Method has failed, here are all method arguments #{SOMETHING}"    
end

Yani, her birini listelemeden yönteme aktarılan tüm argümanları almanın bir yolunu arıyorum. Bu Ruby olduğundan, bir yolu olduğunu varsayıyorum :) eğer java olsaydı sadece listelerdim :)

Çıktı şu şekilde olacaktır:

Method has failed, here are all method arguments {"Mario", "Super", 40, true, true}

1
Reha kralj svegami!
karınca

1
Bence tüm cevaplar, argüman bulma yöntemi çalıştırılmadan önce "bazı kodlar" argümanların değerlerini değiştirirse, aktarılan değerleri değil, yeni değerleri göstereceğini belirtmelidir. emin olmak için uzakta. Bununla birlikte, bunun için en sevdiğim tek method(__method__).parameters.map { |_, v| [v, binding.local_variable_get(v)] }
satırlık yazım (

Yanıtlar:


160

Ruby 1.9.2 ve sonrasında, parametersyöntemi bir yöntemde, o yöntemin parametrelerinin listesini almak için kullanabilirsiniz. Bu, parametrenin adını ve gerekli olup olmadığını gösteren bir çift listesi döndürür.

Örneğin

Yaparsan

def foo(x, y)
end

sonra

method(:foo).parameters # => [[:req, :x], [:req, :y]]

__method__Mevcut yöntemin adını almak için özel değişkeni kullanabilirsiniz . Dolayısıyla bir yöntem içinde parametrelerinin adları şu yolla elde edilebilir:

args = method(__method__).parameters.map { |arg| arg[1].to_s }

Daha sonra her parametrenin adını ve değerini şu şekilde görüntüleyebilirsiniz:

logger.error "Method failed with " + args.map { |arg| "#{arg} = #{eval arg}" }.join(', ')

Not: Bu cevap orijinal olarak yazıldığından, Ruby'nin mevcut sürümlerinde evalartık bir sembolle çağrılamaz. Bunu ele almak to_siçin, parametre adları listesi oluşturulurken açık bir şekilde eklenmiştir, örn.parameters.map { |arg| arg[1].to_s }


4
Bunu çözmek için biraz zamana ihtiyacım olacak :)
Haris Krajina

3
Hangi bitlerin deşifre edilmesi gerektiğini bana bildirin ve biraz açıklama ekleyeceğim :)
mikej

3
+1 bu gerçekten harika ve zarif; kesinlikle en iyi cevap.
Andrew Marshall

5
Ruby 1.9.3 ile denedim ve çalışması için # {eval arg.to_s} yapmanız gerekiyor, aksi takdirde bir TypeError elde edersiniz: Symbol
String'e

5
Bu arada, daha iyi ve becerilerim arttı ve şimdi bu kodu anladım.
Haris Krajina

55

Ruby 2.1'den beri , yöntem parametreleri (argümanlar) dahil olmak üzere herhangi bir yerel değişkenin değerini okumak için binding.local_variable_get'i kullanabilirsiniz . Bunun sayesinde iyileştirebilirsiniz kaçınmak kabul edilen cevabıkötü eval.

def foo(x, y)
  method(__method__).parameters.map do |_, name|
    binding.local_variable_get(name)
  end
end

foo(1, 2)  # => 1, 2

en erken 2,1 mi?
uchuugaka

@uchuugaka Evet, bu yöntem <2.1'de mevcut değildir.
Jakub Jirutka

Teşekkürler. Bu, bunu güzelleştirir: logger.info yöntemi __ + "# {args.inspect}" yöntemi ( _method ) .parameters.map do | , isim | logger.info "# {name} =" + binding.local_variable_get (name) end
Martin Cleaver

Gitmenin yolu bu.
Ardee Aram

1
Ayrıca potansiyel olarak faydalı - adlandırılmış karma argümanlar dönüştürülmesi:Hash[method(__method__).parameters.map.collect { |_, name| [name, binding.local_variable_get(name)] }]
sheba

19

Bunu halletmenin bir yolu şudur:

def foo(*args)
    first_name, last_name, age, sex, is_plumber = *args
    # some code
    # error happens here
    logger.error "Method has failed, here are all method arguments #{args.inspect}"    
end

2
Çalışıyor ve daha iyi cevaplar olmadığı sürece kabul edilmiş olarak oylanacak, bununla ilgili tek sorunum yöntem imzasını kaybetmek istemiyorum, bazıları orada Inteli duygusu olacak ve onu kaybetmekten nefret ediyorum.
Haris Krajinalı

7

Bu ilginç bir soru. Belki local_variables kullanarak ? Ama eval kullanmaktan başka bir yol olmalı. Kernel belgesine bakıyorum

class Test
  def method(first, last)
    local_variables.each do |var|
      puts eval var.to_s
    end
  end
end

Test.new().method("aaa", 1) # outputs "aaa", 1

Bu o kadar da kötü değil, neden bu kötü çözüm?
Haris Krajina

Bu durumda kötü değil - eval () kullanmak bazen güvenlik açıklarına neden olabilir. Daha iyi bir yol olabileceğini düşünüyorum :) ancak Google'ın bu durumda arkadaşımız olmadığını kabul ediyorum
Raffaele

Bununla devam edeceğim, dezavantajı, bununla ilgilenecek bir yardımcı (modül) yapamazsınız, çünkü orijinal yöntemden ayrılır ayrılmaz yerel değişkenlerin değerlendirmesini yapamaz. Bilgi için hepinize teşekkürler.
Haris Krajina

Bu bana "TypeError: Sembolü Dizeye dönüştürülemiyor" değerini olarak değiştirmediğim sürece veriyor eval var.to_s. Ayrıca, bu döngüyü çalıştırmadan önce herhangi bir yerel değişken tanımlarsanız , bunların yöntem parametrelerine ek olarak dahil edilecekleridir.
Andrew Marshall

6
Bu en zarif ve güvenli yaklaşım değildir - yönteminizin içinde yerel değişken tanımlar ve ardından çağırırsanız local_variables, yöntem bağımsız değişkenlerini + tüm yerel değişkenleri döndürür. Bu, kodunuz olduğunda hatalara neden olabilir.
Aliaksei Kliuchnikau

5

Bu yardımcı olabilir ...

  def foo(x, y)
    args(binding)
  end

  def args(callers_binding)
    callers_name = caller[0][/`.*'/][1..-2]
    parameters = method(callers_name).parameters
    parameters.map { |_, arg_name|
      callers_binding.local_variable_get(arg_name)
    }    
  end

1
Aksine bu biraz hacky daha callers_nameuygulanması, ayrıca geçebileceği __method__birlikte binding.
Tom Lord

3

Aşağıdaki gibi bir sabit tanımlayabilirsiniz:

ARGS_TO_HASH = "method(__method__).parameters.map { |arg| arg[1].to_s }.map { |arg| { arg.to_sym => eval(arg) } }.reduce Hash.new, :merge"

Ve bunu kodunuzda şu şekilde kullanın:

args = eval(ARGS_TO_HASH)
another_method_that_takes_the_same_arguments(**args)

2

Daha ileri gitmeden önce, foo'ya çok fazla argüman iletiyorsun. Görünüşe göre tüm bu argümanlar bir Modeldeki özellikler, değil mi? Gerçekten nesnenin kendisini iletmelisiniz. Konuşmanın Sonu.

Bir "uyarma" argümanı kullanabilirsiniz. Her şeyi bir diziye sokar. Şöyle görünür:

def foo(*bar)
  ...
  log.error "Error with arguments #{bar.joins(', ')}"
end

Buna katılmıyorum, yöntem imzası kodun okunabilirliği ve yeniden kullanılabilirliği için önemlidir. Nesnenin kendisi iyidir, ancak bir yerde örnek oluşturmanız gerekir, bu nedenle yöntemi veya yöntemi çağırmadan önce. Bence yöntemde daha iyi. örneğin, kullanıcı yöntemi oluşturun.
Haris Krajina

Benden daha zeki bir adam olan Bob Martin'in Clean Code adlı kitabından alıntı yapacak olursak, "Bir işlev için ideal argüman sayısı sıfırdır (niladik). Sonra bir (monoadik) gelir, ardından iki (ikili). Üç argüman (triadik) mümkün olduğunca kaçınılmalıdır. Üçten fazlası (poliadik) çok özel gerekçelendirme gerektirir ve bu durumda yine de kullanılmamalıdır. " Söylediğimi söylemeye devam ediyor, birçok ilgili argüman bir sınıfa sarılmalı ve bir nesne olarak aktarılmalıdır. İyi bir kitap, kesinlikle tavsiye ederim.
Tom L

Çok ince bir noktaya değinmek değil, ama şunu göz önünde bulundurun: Daha fazla / daha az / farklı argümanlara ihtiyacınız olduğunu fark ederseniz, API'nizi kırmış olacaksınız ve bu yönteme yapılan her çağrıyı güncellemeniz gerekecektir. Öte yandan, bir nesneyi iletirseniz, uygulamanızın diğer bölümleri (veya hizmetinizin tüketicileri) neşeyle yan yana gidebilir.
Tom L

Puanlarınıza katılıyorum ve örneğin Java'da yaklaşımınızı her zaman uygularım. Ama ROR ile farklı olduğunu düşünüyorum ve işte nedeni:
Haris Krajina

Puanlarınıza katılıyorum ve örneğin Java'da yaklaşımınızı her zaman uygularım. Ama ROR ile farklı olduğunu düşünüyorum ve işte nedeni: ActiveRecord'u DB'ye kaydetmek istiyorsanız ve onu kaydeden bir yönteme sahipseniz, yöntemi kaydetmeye geçmeden önce hash'i birleştirmeniz gerekir. Kullanıcı örneği için, ad, soyad, kullanıcı adı vb. Ayarladık ve daha sonra bir şeyler yapacak ve onu kaydedecek olan kaydetme yöntemini geçmek için hash geçirdik. Ve işte sorun, her geliştirici hash'e ne koyacağını nasıl biliyor? Aktif kayıt olduğundan, hash'i birleştirmektense db şemasını görmeniz ve herhangi bir sembolü kaçırmamaya çok dikkat etmeniz gerekir.
Haris Krajina

2

Yöntem imzasını değiştirirseniz, şöyle bir şey yapabilirsiniz:

def foo(*args)
  # some code
  # error happens here
  logger.error "Method has failed, here are all method arguments #{args}"    
end

Veya:

def foo(opts={})
  # some code
  # error happens here
  logger.error "Method has failed, here are all method arguments #{opts.values}"    
end

Bu durumda, enterpolasyonlu argsveya opts.valuesbir dizi olacaktır, ancak joinvirgül üzerindeyse yapabilirsiniz . Şerefe


2

Görünüşe göre bu sorunun başarmaya çalıştığı şey, yeni çıkardığım bir mücevher ile yapılabilir, https://github.com/ericbeland/exception_details . Kurtarılan istisnalardan yerel değişkenleri ve değerleri (ve örnek değişkenleri) listeleyecektir. Bir bakmaya değer olabilir ...


1
Bu güzel bir cevher, Rails kullanıcıları için ayrıca gem'i tavsiye ederim better_errors.
Haris Krajina

1

Bir Hash olarak argümanlara ihtiyacınız varsa ve metodun gövdesini karmaşık parametrelerin çıkarılmasıyla kirletmek istemiyorsanız, şunu kullanın:

def mymethod(firstarg, kw_arg1:, kw_arg2: :default)
  args = MethodArguments.(binding) # All arguments are in `args` hash now
  ...
end

Sadece bu sınıfı projenize ekleyin:

class MethodArguments
  def self.call(ext_binding)
    raise ArgumentError, "Binding expected, #{ext_binding.class.name} given" unless ext_binding.is_a?(Binding)
    method_name = ext_binding.eval("__method__")
    ext_binding.receiver.method(method_name).parameters.map do |_, name|
      [name, ext_binding.local_variable_get(name)]
    end.to_h
  end
end

1

İşlev bir sınıfın içindeyse, şöyle bir şey yapabilirsiniz:

class Car
  def drive(speed)
  end
end

car = Car.new
method = car.method(:drive)

p method.parameters #=> [[:req, :speed]] 
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.