Có cách nào để truy cập các đối số phương thức trong Ruby không?


102

Mới sử dụng Ruby và ROR và yêu thích nó mỗi ngày, vì vậy đây là câu hỏi của tôi vì tôi không biết làm thế nào để google nó (và tôi đã thử :))

chúng tôi có phương pháp

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

Vì vậy, những gì tôi đang tìm cách để chuyển tất cả các đối số đến phương thức mà không cần liệt kê từng đối số. Vì đây là Ruby nên tôi cho rằng có một cách :) nếu đó là java tôi sẽ liệt kê chúng :)

Đầu ra sẽ là:

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

1
Reha kralj svegami!
ant,

1
Tôi nghĩ rằng tất cả các câu trả lời sẽ chỉ ra rằng nếu "một số mã" thay đổi giá trị của các đối số trước khi phương thức khám phá đối số được chạy, nó sẽ hiển thị các giá trị mới, không phải các giá trị đã được chuyển vào. Vì vậy, bạn nên nắm bắt chúng ngay đi để chắc chắn. Điều đó nói rằng, yêu thích một lót của tôi cho điều này (với tín dụng cho các câu trả lời trước) là:method(__method__).parameters.map { |_, v| [v, binding.local_variable_get(v)] }
Brian Deterling

Câu trả lời:


159

Trong Ruby 1.9.2 trở lên, bạn có thể sử dụng parametersphương thức trên một phương thức để lấy danh sách các tham số cho phương thức đó. Thao tác này sẽ trả về danh sách các cặp cho biết tên của tham số và liệu nó có được yêu cầu hay không.

ví dụ

Nếu bạn làm

def foo(x, y)
end

sau đó

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

Bạn có thể sử dụng biến đặc biệt __method__để lấy tên của phương thức hiện tại. Vì vậy, trong một phương thức, tên của các tham số của nó có thể được lấy qua

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

Sau đó, bạn có thể hiển thị tên và giá trị của từng tham số với

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

Lưu ý: vì câu trả lời này ban đầu được viết nên trong các phiên bản hiện tại của Ruby evalkhông còn có thể được gọi bằng ký hiệu nữa. Để giải quyết vấn đề này, một định nghĩa rõ ràng to_sđã được thêm vào khi xây dựng danh sách các tên tham số, tức làparameters.map { |arg| arg[1].to_s }


4
Tôi sẽ cần một thời gian để giải mã :) này
Haris Krajina

3
Hãy cho tôi biết mà bit cần giải mã và tôi sẽ thêm một số lời giải thích :)
mikej

3
+1 điều này thực sự tuyệt vời và thanh lịch; chắc chắn là câu trả lời tốt nhất.
Andrew Marshall

5
Tôi cố gắng với Ruby 1.9.3, và bạn phải làm # {arg.to_s eval} để làm cho nó làm việc, nếu không bạn có được một Lỗi Loại: không thể chuyển đổi Symbol vào Chuỗi
Javid Jamae

5
Trong khi đó, đã tốt hơn và kỹ năng của tôi và hiểu mã này ngay bây giờ.
Haris Krajina

55

Kể từ Ruby 2.1, bạn có thể sử dụng bind.local_variable_get để đọc giá trị của bất kỳ biến cục bộ nào, bao gồm các tham số phương thức (đối số). Nhờ đó bạn có thể cải thiện câu trả lời được chấp nhận để tránhtà ác đánh giá.

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

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

2.1 là sớm nhất?
uchuugaka

@uchuugaka Vâng, phương pháp này không khả dụng trong <2.1.
Jakub Jirutka

Cảm ơn. Điều đó làm cho điều này tốt đẹp: phương thức logger.info __ + Phương thức "# {args.inspect}" ( _method ) .parameters.map do | , tên | logger.info "# {name} =" + binding.local_variable_get (name) end
Martin Cleaver

Đây là con đường để đi.
Ardee Aram

1
Cũng có khả năng hữu ích - chuyển đổi các đối số thành một hàm băm được đặt tên:Hash[method(__method__).parameters.map.collect { |_, name| [name, binding.local_variable_get(name)] }]
sheba

19

Một cách để xử lý điều này là:

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
Đang làm việc và sẽ được bỏ phiếu là chấp nhận trừ khi có câu trả lời tốt hơn, vấn đề duy nhất của tôi với điều này là tôi không muốn mất chữ ký của phương thức, một số sẽ có Inteli sense và tôi rất ghét mất nó.
Haris Krajina

7

Đây là một câu hỏi thú vị. Có thể sử dụng local_variables ? Nhưng phải có một cách khác ngoài việc sử dụng eval. Tôi đang tìm trong tài liệu Kernel

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

Cái này cũng không tệ lắm, tại sao lại có giải pháp ác này?
Haris Krajina

Nó không tệ trong trường hợp này - việc sử dụng eval () đôi khi có thể dẫn đến các lỗ hổng bảo mật. Chỉ cần tôi nghĩ rằng có thể có một cách tốt hơn :) nhưng tôi thừa nhận Google không phải là bạn của chúng tôi trong trường hợp này
Raffaele

Tôi sẽ đi với điều này, nhược điểm là bạn không thể tạo helper (mô-đun) sẽ xử lý việc này, vì ngay sau khi nó rời khỏi phương thức ban đầu, nó không thể thực hiện việc né tránh các vars cục bộ. Cảm ơn tất cả các thông tin.
Haris Krajina

Điều này mang lại cho tôi "TypeError: không thể chuyển đổi Biểu tượng thành Chuỗi" trừ khi tôi thay đổi nó thành eval var.to_s. Ngoài ra, một lưu ý cho điều này là nếu bạn xác định bất kỳ biến cục bộ nào trước khi chạy vòng lặp này, chúng sẽ được đưa vào cùng với các tham số phương thức.
Andrew Marshall

6
Đây không phải là cách tiếp cận an toàn và thanh lịch nhất - nếu bạn xác định biến cục bộ bên trong phương thức của mình và sau đó gọi local_variables, nó sẽ trả về đối số phương thức + tất cả các biến cục bộ. Điều này có thể gây ra lỗi khi mã của bạn.
Aliaksei Kliuchnikau

5

Điều này có thể hữu ích ...

  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
Thay vì một chút hacky này callers_namethực hiện, bạn cũng có thể vượt qua __method__cùng với binding.
Tom Lord,

3

Bạn có thể xác định một hằng số chẳng hạn như:

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

Và sử dụng nó trong mã của bạn như:

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

2

Trước khi tôi đi xa hơn, bạn đang chuyển quá nhiều đối số vào foo. Có vẻ như tất cả các đối số đó đều là thuộc tính trên Mô hình, đúng không? Bạn thực sự nên chuyển chính đối tượng. Kết thúc bài phát biểu.

Bạn có thể sử dụng đối số "biểu tượng". Nó đẩy mọi thứ vào một mảng. Nó sẽ trông giống như:

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

Không đồng ý về điều này, chữ ký phương pháp rất quan trọng đối với khả năng đọc và khả năng sử dụng lại của mã. Đối tượng tự nó là tốt, nhưng bạn phải tạo cá thể ở đâu đó, vì vậy trước khi gọi phương thức hoặc trong phương thức. Tốt hơn về phương pháp theo ý kiến ​​của tôi. ví dụ: tạo phương thức người dùng.
Haris Krajina,

Để trích dẫn một người đàn ông thông minh hơn tôi, Bob Martin, trong cuốn sách Clean Code của anh ấy, "số đối số lý tưởng cho một hàm là 0 (niladic). Tiếp theo là một (monoadic), tiếp theo là hai (dyadic). Ba đối số (triadic) nên tránh nếu có thể. Nhiều hơn ba (polyadic) đòi hỏi sự biện minh rất đặc biệt - và sau đó không nên sử dụng. " Anh ấy tiếp tục nói những gì tôi đã nói, nhiều đối số liên quan nên được gói trong một lớp và được chuyển như một đối tượng. Đó là một cuốn sách hay, tôi rất khuyên bạn nên dùng nó.
Tom L

Không nên đặt nặng vấn đề, nhưng hãy cân nhắc điều này: nếu bạn thấy rằng bạn cần nhiều / ít hơn / các đối số khác nhau thì bạn sẽ làm hỏng API của mình và phải cập nhật mọi lệnh gọi đến phương thức đó. Mặt khác, nếu bạn chuyển một đối tượng thì các phần khác trong ứng dụng của bạn (hoặc người tiêu dùng dịch vụ của bạn) có thể vui vẻ chạy theo.
Tom L

Tôi đồng ý với quan điểm của bạn và ví dụ: trong Java, tôi sẽ luôn thực thi cách tiếp cận của bạn. Nhưng tôi nghĩ rằng với ROR là khác nhau và đây là lý do:
Haris Krajina

Tôi đồng ý với quan điểm của bạn và ví dụ: trong Java, tôi sẽ luôn thực thi cách tiếp cận của bạn. Nhưng tôi nghĩ rằng với ROR thì khác và đây là lý do tại sao: Nếu bạn muốn lưu ActiveRecord vào DB và bạn có phương thức lưu nó, bạn sẽ phải tập hợp hàm băm trước khi tôi chuyển nó vào phương thức lưu. Đối với ví dụ người dùng, chúng tôi đặt đầu tiên, họ, tên người dùng, v.v. và vượt qua phương thức băm để lưu sẽ thực hiện điều gì đó và lưu nó. Và đây là vấn đề làm thế nào để mọi nhà phát triển biết những gì cần đưa vào hash? Đó là bản ghi đang hoạt động, vì vậy bạn sẽ phải xem lược đồ db hơn là lắp ráp băm và rất cẩn thận để không bỏ lỡ bất kỳ ký hiệu nào.
Haris Krajina

2

Nếu bạn muốn thay đổi chữ ký của phương thức, bạn có thể làm như sau:

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

Hoặc là:

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

Trong trường hợp này, nội suy argshoặc opts.valuessẽ là một mảng, nhưng bạn có thể joinnếu trên dấu phẩy. Chúc mừng


2

Có vẻ như những gì câu hỏi này đang cố gắng hoàn thành có thể được thực hiện với một viên ngọc mà tôi vừa phát hành, https://github.com/ericbeland/exception_details . Nó sẽ liệt kê các biến cục bộ và vlaues (và các biến thể hiện) từ các ngoại lệ được giải cứu. Có thể là một giá trị...


1
Đó là một viên ngọc tuyệt vời, đối với người dùng Rails, tôi cũng muốn giới thiệu better_errorsđá quý.
Haris Krajina

1

Nếu bạn cần đối số dưới dạng Hash và bạn không muốn làm ô nhiễm phần thân của phương thức bằng cách trích xuất phức tạp các tham số, hãy sử dụng cái này:

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

Chỉ cần thêm lớp này vào dự án của bạn:

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

Nếu hàm nằm trong một lớp nào đó thì bạn có thể làm như sau:

class Car
  def drive(speed)
  end
end

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

p method.parameters #=> [[:req, :speed]] 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.