Ruby Metaprogramming: tên biến phiên bản động


94

Giả sử tôi có hàm băm sau:

{ :foo => 'bar', :baz => 'qux' }

Làm cách nào để tôi có thể đặt động các khóa và giá trị để trở thành các biến phiên bản trong một đối tượng ...

class Example
  def initialize( hash )
    ... magic happens here...
  end
end

... để tôi kết thúc với phần sau bên trong mô hình ...

@foo = 'bar'
@baz = 'qux'

?

Câu trả lời:


168

Phương pháp bạn đang tìm kiếm là instance_variable_set. Vì thế:

hash.each { |name, value| instance_variable_set(name, value) }

Hay ngắn gọn hơn,

hash.each &method(:instance_variable_set)

Nếu tên biến cá thể của bạn thiếu "@" (như trong ví dụ của OP), bạn sẽ cần thêm chúng, vì vậy nó sẽ giống như sau:

hash.each { |name, value| instance_variable_set("@#{name}", value) }

18
Không phù hợp với tôi cho 1.9.3. Tôi sử dụng này để thay thếhash.each {|k,v| instance_variable_set("@#{k}",v)}
Andrei

3
thêm một lý do để tình yêu của Ruby
jschorr

bạn có thể vui lòng giải thích làm thế nào trong hash.each &method(:instance_variable_set), phương thức instance_variable_setnhận được hai tham số mà nó cần?
Arnold Roa

bất kỳ ý tưởng làm thế nào để làm điều này một cách đệ quy? (nếu có mức độ nhiều trong băm đầu vào)
nemenems

13
h = { :foo => 'bar', :baz => 'qux' }

o = Struct.new(*h.keys).new(*h.values)

o.baz
 => "qux" 
o.foo
 => "bar" 

1
Điều đó khá thú vị ... chính xác thì chuỗi thứ hai .new()đang làm gì?
Andrew

3
@Andrew: Struct.newtạo một lớp mới dựa trên các khóa băm và sau đó lớp thứ hai newtạo đối tượng đầu tiên của lớp vừa tạo, khởi tạo nó thành các giá trị của Hash. Xem ruby-doc.org/core-1.8.7/classes/Struct.html
DigitalRoss

2
Đây thực sự là một cách thực sự tuyệt vời để làm điều đó vì đây là khá nhiều thứ mà Struct được tạo ra.
Chuck

2
Hoặc sử dụng OpenStruct . require 'ostruct'; h = {:foo => 'foo'}; o = OpenStruct.new(h); o.foo == 'foo'
Justin Force

Tôi đã phải ánh xạ chìa khóa của mình tới các ký hiệu:Struct.new(*hash.keys.map { |str| str.to_sym }).new(*hash.values)
erran

7

Bạn khiến chúng tôi muốn khóc :)

Trong mọi trường hợp, hãy xem Object#instance_variable_getObject#instance_variable_set.

Chúc bạn viết mã vui vẻ.


vâng, tôi không thể không tự hỏi ... tại sao? Khi nào là thời điểm tốt để sử dụng cái này?
Zach Smith

ví dụ: tôi có thể muốn có một lệnh set_entitygọi lại chung cho tất cả các bộ điều khiển và tôi không muốn can thiệp vào các biến phiên bản hiện códef set_entity(name, model); instance_variable_set(name, model.find_by(params[:id])); end;
user1201917

5

Bạn cũng có thể sử dụng cách sendnày để ngăn người dùng đặt các biến phiên bản không tồn tại:

def initialize(hash)
  hash.each { |key, value| send("#{key}=", value) }
end

Sử dụng sendkhi trong lớp của bạn có một bộ cài đặt như attr_accessorcho các biến phiên bản của bạn:

class Example
  attr_accessor :foo, :baz
  def initialize(hash)
    hash.each { |key, value| send("#{key}=", value) }
  end
end
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.