มีวิธีเข้าถึงข้อโต้แย้งวิธีการใน Ruby หรือไม่?


102

ใหม่กับ Ruby และ ROR และรักมันทุกวันดังนั้นนี่คือคำถามของฉันเนื่องจากฉันไม่รู้ว่าจะใช้ Google อย่างไร (และฉันได้ลองแล้ว :))

เรามีวิธีการ

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

ดังนั้นสิ่งที่ฉันกำลังมองหาวิธีที่จะส่งข้อโต้แย้งทั้งหมดไปยังวิธีการโดยไม่ต้องระบุรายการแต่ละรายการ เนื่องจากนี่คือ Ruby ฉันคิดว่ามีวิธี :) ถ้าเป็น java ฉันจะแสดงรายการเหล่านั้น :)

ผลลัพธ์จะเป็น:

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

1
Reha kralj svegami!
มด

1
ฉันคิดว่าคำตอบทั้งหมดควรชี้ให้เห็นว่าหาก "โค้ดบางส่วน" เปลี่ยนค่าของอาร์กิวเมนต์ก่อนที่จะรันเมธอดการค้นพบอาร์กิวเมนต์จะแสดงค่าใหม่ไม่ใช่ค่าที่ส่งผ่านดังนั้นคุณควรจับมันให้ถูกต้อง เพื่อความแน่ใจ ที่กล่าวว่าหนึ่งซับที่ฉันชอบสำหรับเรื่องนี้ (พร้อมเครดิตที่ให้กับคำตอบก่อนหน้านี้) คือ:method(__method__).parameters.map { |_, v| [v, binding.local_variable_get(v)] }
Brian Deterling

คำตอบ:


159

ใน Ruby 1.9.2 และใหม่กว่าคุณสามารถใช้parametersเมธอดบนเมธอดเพื่อรับรายการพารามิเตอร์สำหรับเมธอดนั้น สิ่งนี้จะส่งคืนรายการคู่ที่ระบุชื่อของพารามิเตอร์และระบุว่าจำเป็นหรือไม่

เช่น

ถ้าคุณทำ

def foo(x, y)
end

แล้ว

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

คุณสามารถใช้ตัวแปรพิเศษ__method__เพื่อรับชื่อของเมธอดปัจจุบัน ดังนั้นภายในเมธอดสามารถรับชื่อของพารามิเตอร์ได้ทาง

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

จากนั้นคุณสามารถแสดงชื่อและค่าของแต่ละพารามิเตอร์ด้วย

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

หมายเหตุ:เนื่องจากคำตอบนี้ถูกเขียนขึ้นในตอนแรก Ruby ในเวอร์ชันปัจจุบันevalจึงไม่สามารถเรียกด้วยสัญลักษณ์ได้อีกต่อไป เพื่อแก้ไขปัญหานี้to_sได้มีการเพิ่มExplicit เมื่อสร้างรายการชื่อพารามิเตอร์เช่นparameters.map { |arg| arg[1].to_s }


4
ฉันจะต้องใช้เวลาในการถอดรหัส :)
Haris Krajina

3
แจ้งให้เราทราบว่าบิตใดที่ต้องถอดรหัสและฉันจะเพิ่มคำอธิบายให้ :)
mikej

3
+1 นี่ยอดเยี่ยมและสง่างามจริงๆ คำตอบที่ดีที่สุดแน่นอน
Andrew Marshall

5
ฉันลองใช้ Ruby 1.9.3 และคุณต้องทำ # {eval arg.to_s} เพื่อให้มันใช้งานได้มิฉะนั้นคุณจะได้รับ TypeError: ไม่สามารถแปลง Symbol เป็น String ได้
Javid Jamae

5
ในขณะเดียวกันก็เก่งขึ้นและทักษะของฉันและเข้าใจรหัสนี้ทันที
Haris Krajina

55

เนื่องจาก Ruby 2.1 คุณสามารถใช้binding.local_variable_getเพื่ออ่านค่าของตัวแปรโลคัลรวมทั้งพารามิเตอร์วิธีการ (อาร์กิวเมนต์) ขอบคุณที่คุณสามารถปรับปรุงคำตอบที่ยอมรับเพื่อหลีกเลี่ยงชั่วร้าย ประเมิน

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

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

2.1 เร็วที่สุด?
uchuugaka

@uchuugaka ใช่วิธีนี้ไม่มีใน <2.1
Jakub Jirutka

ขอบคุณ. ที่ทำให้สิ่งนี้ดี: logger.info method __ + "# {args.inspect}" method ( _method ) .parameters.map do | , ชื่อ | logger.info "# {name} =" + binding.local_variable_get (name) end
Martin Cleaver

นี่คือวิธีที่จะไป
อาดีอร่าม

1
อาจมีประโยชน์เช่นกัน - การเปลี่ยนอาร์กิวเมนต์เป็นแฮชที่มีชื่อ:Hash[method(__method__).parameters.map.collect { |_, name| [name, binding.local_variable_get(name)] }]
sheba

19

วิธีหนึ่งในการจัดการสิ่งนี้คือ:

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
ใช้งานได้และจะได้รับการโหวตว่าเป็นที่ยอมรับเว้นแต่จะมีคำตอบที่ดีกว่าปัญหาเดียวของฉันคือฉันไม่ต้องการสูญเสียลายเซ็นวิธีการบางอย่างจะมีความรู้สึก Inteli และฉันเกลียดที่จะสูญเสียมัน
Haris Krajina

7

นี่เป็นคำถามที่น่าสนใจ อาจใช้local_variables ? แต่ต้องมีวิธีอื่นที่ไม่ใช่การใช้ eval ฉันกำลังมองหาใน Kernel doc

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

นี่มันไม่ได้เลวร้ายขนาดนี้ทำไมวิธีการแก้ปัญหาที่ชั่วร้าย?
Haris Krajina

มันไม่เลวในกรณีนี้ - การใช้ eval () บางครั้งอาจทำให้เกิดช่องโหว่ด้านความปลอดภัย ฉันคิดว่าอาจมีวิธีที่ดีกว่านี้ :) แต่ฉันยอมรับว่า Google ไม่ใช่เพื่อนของเราในกรณีนี้
Raffaele

ฉันจะไปกับสิ่งนี้ข้อเสียคือคุณไม่สามารถสร้างตัวช่วย (โมดูล) ที่จะดูแลสิ่งนี้ได้เนื่องจากทันทีที่มันออกจากวิธีการดั้งเดิมมันไม่สามารถทำการคัดลอกตัวแทนท้องถิ่นได้ ขอบคุณทุกข้อมูล
Haris Krajina

นี้จะช่วยให้ฉัน "TypeError: ไม่สามารถแปลงสัญลักษณ์เพื่อ String" eval var.to_sถ้าฉันเปลี่ยนไป นอกจากนี้ข้อแม้ก็คือหากคุณกำหนดตัวแปรโลคัลใด ๆก่อนที่จะรันลูปนี้ตัวแปรเหล่านี้จะถูกรวมไว้นอกเหนือจากพารามิเตอร์วิธีการ
Andrew Marshall

6
นี่ไม่ใช่วิธีการที่สง่างามและปลอดภัยที่สุด - หากคุณกำหนดตัวแปรโลคัลภายในเมธอดของคุณแล้วเรียกใช้local_variablesมันจะส่งคืนข้อโต้แย้งของเมธอด + ตัวแปรโลคัลทั้งหมด ซึ่งอาจทำให้เกิดข้อผิดพลาดเมื่อรหัสของคุณ
Aliaksei Kliuchnikau

5

นี่อาจเป็นประโยชน์ ...

  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
แทนที่จะcallers_nameใช้งานแฮ็กเล็กน้อยนี้คุณสามารถส่งต่อ__method__ด้วยไฟล์binding.
Tom Lord

3

คุณสามารถกำหนดค่าคงที่เช่น:

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

และใช้ในรหัสของคุณเช่น:

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

2

ก่อนที่ฉันจะไปไกลกว่านี้คุณกำลังมีข้อโต้แย้งเกี่ยวกับ foo มากเกินไป ดูเหมือนว่าอาร์กิวเมนต์ทั้งหมดจะเป็นแอตทริบิวต์ใน Model ใช่ไหม คุณควรจะส่งผ่านวัตถุนั้นจริงๆ สิ้นสุดคำพูด

คุณสามารถใช้อาร์กิวเมนต์ "splat" มันผลักทุกอย่างลงในอาร์เรย์ ดูเหมือนว่า:

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

ไม่เห็นด้วยกับเรื่องนี้ลายเซ็นของวิธีการมีความสำคัญต่อความสามารถในการอ่านและการนำโค้ดกลับมาใช้ใหม่ได้ Object นั้นดี แต่คุณต้องสร้างอินสแตนซ์ที่ไหนสักแห่งดังนั้นก่อนที่คุณจะเรียกใช้เมธอดหรือในเมธอด วิธีการที่ดีกว่าในความคิดของฉัน เช่นสร้างวิธีการของผู้ใช้
Haris Krajina

เพื่ออ้างถึงผู้ชายที่ฉลาดกว่าฉันบ็อบมาร์ตินในหนังสือ Clean Code ของเขา "จำนวนอาร์กิวเมนต์ที่เหมาะสำหรับฟังก์ชันคือศูนย์ (niladic) ถัดมาเป็นหนึ่ง (โมโนดิค) ตามด้วยสอง (dyadic) สามอาร์กิวเมนต์ (triadic) ควรหลีกเลี่ยงหากเป็นไปได้มากกว่าสาม (polyadic) ต้องการเหตุผลพิเศษมาก - และไม่ควรใช้ต่อไป " เขากล่าวต่อไปในสิ่งที่ฉันพูดข้อโต้แย้งที่เกี่ยวข้องจำนวนมากควรรวมอยู่ในชั้นเรียนและส่งผ่านเป็นวัตถุ เป็นหนังสือที่ดีฉันขอแนะนำ
Tom L

อย่าใส่จุดที่ดีเกินไป แต่ให้พิจารณาสิ่งนี้: หากคุณพบว่าคุณต้องการอาร์กิวเมนต์มาก / น้อย / แตกต่างกันแสดงว่า API ของคุณเสียและต้องอัปเดตการเรียกใช้วิธีนั้นทุกครั้ง ในทางกลับกันหากคุณส่งวัตถุส่วนอื่น ๆ ของแอปของคุณ (หรือผู้ใช้บริการของคุณ) ก็สามารถจับคู่กันได้อย่างสนุกสนาน
Tom L

ฉันเห็นด้วยกับคะแนนของคุณและเช่นใน Java ฉันจะบังคับใช้แนวทางของคุณเสมอ แต่ฉันคิดว่ากับ ROR แตกต่างกันและนี่คือเหตุผล:
Haris Krajina

ฉันเห็นด้วยกับคะแนนของคุณและเช่นใน Java ฉันจะบังคับใช้แนวทางของคุณเสมอ แต่ฉันคิดว่ากับ ROR นั้นแตกต่างกันและนี่คือเหตุผล: หากคุณต้องการบันทึก ActiveRecord ไปยัง DB และคุณมีวิธีการที่จะบันทึกคุณจะต้องรวบรวมแฮชก่อนที่จะส่งต่อเพื่อบันทึกวิธีการ ตัวอย่างเช่นผู้ใช้เราตั้งชื่อนามสกุลชื่อผู้ใช้และอื่น ๆ และกว่าจะผ่านแฮชเพื่อบันทึกวิธีการซึ่งจะทำอะไรบางอย่างและบันทึกไว้ และนี่คือปัญหานักพัฒนาทุกคนรู้ได้อย่างไรว่าจะใส่อะไรลงในแฮช? เป็นบันทึกที่ใช้งานอยู่ดังนั้นคุณจะต้องไปดู db schema มากกว่ารวบรวมแฮชและระวังอย่าพลาดสัญลักษณ์ใด ๆ
Haris Krajina

2

หากคุณจะเปลี่ยนลายเซ็นของวิธีการคุณสามารถทำสิ่งนี้:

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

หรือ:

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

ในกรณีนี้จะสอดแทรกargsหรือopts.valuesจะเป็นอาร์เรย์ แต่คุณสามารถทำได้joinหากใช้ลูกน้ำ ไชโย


2

ดูเหมือนว่าสิ่งที่คำถามนี้พยายามที่จะบรรลุสามารถทำได้ด้วยอัญมณีฉันเพิ่งปล่อยตัวhttps://github.com/ericbeland/exception_details มันจะแสดงรายการตัวแปรท้องถิ่นและ vlaues (และตัวแปรอินสแตนซ์) จากข้อยกเว้นที่ได้รับการช่วยเหลือ น่าดู ...


1
นั่นเป็นอัญมณีที่ดีสำหรับผู้ใช้ Rails ฉันอยากจะแนะนำbetter_errorsอัญมณีด้วย
Haris Krajina

1

หากคุณต้องการอาร์กิวเมนต์เป็น Hash และคุณไม่ต้องการสร้างมลพิษให้กับร่างกายของวิธีการด้วยการแยกพารามิเตอร์ที่ยุ่งยากให้ใช้สิ่งนี้:

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

เพียงเพิ่มชั้นเรียนนี้ในโครงการของคุณ:

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

0

หากฟังก์ชันอยู่ในบางคลาสคุณสามารถทำสิ่งนี้ได้:

class Car
  def drive(speed)
  end
end

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

p method.parameters #=> [[:req, :speed]] 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.