2009-11-04 12 views
5

Có cách nào đơn giản để liệt kê các trình truy cập/trình đọc đã được đặt trong lớp Ruby không?Khởi tạo một lớp Ruby từ một băm tùy ý, nhưng chỉ các khóa có các trình kết nối phù hợp

class Test 
    attr_reader :one, :two 

    def initialize 
    # Do something 
    end 

    def three 
    end 
end 

Test.new 
=> [one,two] 

Những gì tôi thực sự cố gắng làm là cho phép khởi tạo chấp nhận băm với bất kỳ số thuộc tính nào, nhưng chỉ cam kết một số có độc giả đã được xác định. Một cái gì đó như:

def initialize(opts) 
    opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val| 
    eval("@#{opt} = \"#{val}\"") 
    end 
end 

Bất kỳ đề xuất nào khác?

Trả lời

9

Đây là những gì tôi sử dụng (tôi gọi đây là thành ngữ băm-init).

def initialize(object_attribute_hash = {}) 
    object_attribute_hash.map { |(k, v)| send("#{k}=", v) } 
end 

Nếu bạn đang ở trên của Ruby 1.9 bạn có thể làm điều đó thậm chí sạch hơn (gửi cho phép các phương pháp tư nhân):

def initialize(object_attribute_hash = {}) 
    object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) } 
end 

này sẽ nâng cao một NoMethodError nếu bạn cố gắng để gán cho foo và phương pháp "foo = " không tồn tại. Nếu bạn muốn làm cho nó sạch sẽ (gán attrs mà tác giả tồn tại), bạn nên làm một tấm séc

def initialize(object_attribute_hash = {}) 
    object_attribute_hash.map do |(k, v)| 
    writer_m = "#{k}=" 
    send(writer_m, v) if respond_to?(writer_m) } 
    end 
end 

tuy nhiên điều này có thể dẫn đến tình huống mà bạn nuôi đối tượng phím sai của bạn (ví dụ từ một hình thức) và thay vì thất bại lớn tiếng nó sẽ nuốt chửng chúng - gỡ lỗi đau đớn phía trước. Vì vậy, trong cuốn sách của tôi, NoMethodError là một lựa chọn tốt hơn (nó có nghĩa là vi phạm hợp đồng).

Nếu bạn chỉ muốn một danh sách của tất cả các nhà văn (không có cách nào để làm điều đó cho độc giả) bạn làm

some_object.methods.grep(/\w=$/) 

đó là "nhận được một loạt các tên phương pháp và grep nó cho các mục mà cuối với một dấu bằng đơn sau ký tự từ ".

Nếu bạn làm

eval("@#{opt} = \"#{val}\"") 

val xuất phát từ một mẫu web - xin chúc mừng, bạn chỉ cần trang bị ứng dụng của bạn với một rộng mở khai thác.

4

Bạn có thể ghi đè attr_reader, attr_writer và attr_accessor để cung cấp một số loại cơ chế theo dõi cho lớp của bạn để bạn có thể có khả năng phản chiếu tốt hơn như thế này.

Ví dụ:

class Class 
    alias_method :attr_reader_without_tracking, :attr_reader 
    def attr_reader(*names) 
    attr_readers.concat(names) 
    attr_reader_without_tracking(*names) 
    end 

    def attr_readers 
    @attr_readers ||= [ ] 
    end 

    alias_method :attr_writer_without_tracking, :attr_writer 
    def attr_writer(*names) 
    attr_writers.concat(names) 
    attr_writer_without_tracking(*names) 
    end 

    def attr_writers 
    @attr_writers ||= [ ] 
    end 

    alias_method :attr_accessor_without_tracking, :attr_accessor 
    def attr_accessor(*names) 
    attr_readers.concat(names) 
    attr_writers.concat(names) 
    attr_accessor_without_tracking(*names) 
    end 
end 

Đây có thể được chứng minh khá đơn giản:

class Foo 
    attr_reader :foo, :bar 
    attr_writer :baz 
    attr_accessor :foobar 
end 

puts "Readers: " + Foo.attr_readers.join(', ') 
# => Readers: foo, bar, foobar 
puts "Writers: " + Foo.attr_writers.join(', ') 
# => Writers: baz, foobar 
+0

Đã cập nhật để sử dụng Array # concat thay vì Array # << để tạo mảng phẳng. – tadman

+0

Ý tưởng tuyệt vời! Sẽ giữ cái này trong thư viện ở đâu đó, tôi chắc rằng nó sẽ rất tiện dụng! –

0

Accessors chỉ là phương pháp thông thường điều đó xảy ra để truy cập một số mảnh của dữ liệu. Đây là mã sẽ làm gần như những gì bạn muốn. Nó sẽ kiểm tra nếu có một phương thức có tên cho phím băm và thiết lập một biến Ví dụ kèm theo nếu có:

def initialize(opts) 
    opts.each do |opt,val| 
    instance_variable_set("@#{opt}", val.to_s) if respond_to? opt 
    end 
end 

Lưu ý rằng điều này sẽ được vấp lên nếu một chìa khóa có tên giống như một phương pháp nhưng phương pháp đó không phải là truy cập biến mẫu đơn giản (ví dụ: {:object_id => 42}). Nhưng không phải tất cả những người truy cập nhất thiết cũng sẽ được xác định bởi attr_accessor hoặc là, vì vậy không có cách nào tốt hơn để nói. Tôi cũng đã thay đổi nó để sử dụng instance_variable_set, đó là rất nhiều hiệu quả hơn và an toàn nó là vô lý.

0

Không có cách tích hợp để có danh sách như vậy. Các hàm attr_* về cơ bản chỉ thêm các phương thức, tạo một biến mẫu và không có gì khác. Bạn có thể viết trình bao bọc cho họ để làm những gì bạn muốn, nhưng đó có thể là quá mức cần thiết. Tùy thuộc vào hoàn cảnh cụ thể của bạn, bạn có thể sử dụng Object#instance_variable_defined?Module#public_method_defined?.

Ngoài ra, tránh sử dụng eval khi có thể:

def initialize(opts) 
    opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val| 
    instance_variable_set "@#{opt}", val 
    end 
end 
2

Hãy thử một cái gì đó như thế này:

class Test 
    attr_accessor :foo, :bar 

    def initialize(opts = {}) 
    opts.each do |opt, val| 
     send("#{opt}=", val) if respond_to? "#{opt}=" 
    end 
    end 
end 

test = Test.new(:foo => "a", :bar => "b", :baz => "c") 

p test.foo # => nil 
p test.bar # => nil 
p test.baz # => undefined method `baz' for #<Test:0x1001729f0 @bar="b", @foo="a"> (NoMethodError) 

này về cơ bản là những gì Rails làm khi bạn vượt qua trong một băm params đến mới. Nó sẽ bỏ qua tất cả các tham số mà nó không biết, và nó sẽ cho phép bạn thiết lập những thứ không nhất thiết được xác định bởi attr_accessor, nhưng vẫn có một setter thích hợp.

Nhược điểm duy nhất là điều này thực sự đòi hỏi bạn phải có một setter được xác định (so với chỉ accessor) mà có thể không phải là những gì bạn đang tìm kiếm.

0

Bạn có thể xem để biết phương pháp nào được xác định (với Object#methods) và từ những người xác định bộ định cư (ký tự cuối cùng là =), nhưng không có cách chắc chắn 100% để biết rằng các phương pháp đó không được triển khai theo một cách không rõ ràng liên quan đến các biến mẫu khác nhau.

Tuy nhiên Foo.new.methods.grep(/=$/) sẽ cung cấp cho bạn danh sách các trình cài đặt thuộc tính có thể in được.Hoặc, vì bạn đã có một băm, bạn có thể thử:

def initialize(opts) 
    opts.each do |opt,val| 
    instance_variable_set("@#{opt}", val.to_s) if respond_to? "#{opt}=" 
    end 
end 
+0

Điều này cũng liệt kê các phương thức bằng và triquals vì vậy tôi thêm \ w trước đó – Julik

Các vấn đề liên quan