2015-04-07 12 views
20

Dưới đây là một ví dụ mã:Phát hiện tầm nhìn phương pháp mới trong phương pháp lớp

class Foo 
    def self.create_method 
    def example_method 
     "foo" 
    end 
    end 

    private 

    create_method 
end 

Foo.public_instance_methods(false) # => [:example_method] 

Có thể phát hiện rằng phương pháp lớp create_method được gọi từ lớp Foo khu vực tư nhân?

Trong ví dụ trên, thông tin đó có thể được sử dụng để đặt example_method công khai hoặc riêng tư tùy thuộc vào địa điểm mà create_method được gọi.

Trả lời

2

Mặc dù đó là một chút hacky, có thể:

class Foo 
    def self.create_method 
    define_method :example_method do 
     visibility = case caller(0).first[/block in (\w+)'/, 1].to_sym 
        when ->(m) { Foo.private_methods.include? m } 
         :private 
        when ->(m) { Foo.protected_methods.include? m } 
         :protected 
        when ->(m) { Foo.public_methods.include? m } 
         :public 
        else :unknown 
        end 
     puts "Visibility: #{visibility}" 
    end 
    end 

    private_class_method :create_method 
end 

Foo.send :create_method 
Foo.new.example_method 

#⇒ Visibility: private 

Dưới đây chúng tôi kiểm tra sự hiện diện của người gọi bằng case khối. Xin lưu ý rằng bạn không thể di chuyển trường hợp này sang một phương thức trợ giúp khác mà không có bất kỳ sửa đổi nào, vì nó dựa trên caller. Hy vọng nó giúp.

2

Tôi đã viết giải pháp thống nhất hơn, nó có thể tìm ra phạm vi hiển thị của bất kỳ người gọi nào.

ý tưởng chính của tôi là xác định 2 điều:

  • đối tượng người gọi (self ràng buộc của người gọi) tên
  • phương pháp của đối tượng gọi

tôi đã sử dụng binding_of_caller đá quý để đạt được điều này.

class Foo 
    class << self 
    def visibility_scope 
     binding_of_caller = binding.of_caller(1) 
     caller_method = binding_of_caller.eval('__method__') 
     caller_object = binding_of_caller.eval('self') 

     # It's asking if caller is a module, since Class is inherited from Module 
     if caller_object.is_a?(Module) 
     return visibility_scope_for(caller_object.singleton_class, caller_method) 
     end 


     # First we should check object.singleton_class, since methods from there are called before 
     # class instance methods from object.class 
     visibility = visibility_scope_for(caller_object.singleton_class, caller_method) 
     return visibility if visibility 

     # Then we check instance methods, that are stored in object.class 
     visibility = visibility_scope_for(caller_object.class, caller_method) 
     return visibility if visibility 

     fail 'Visibility is undefined' 
    end 

    private 

    def visibility_scope_for(object, method_name) 
     %w(public protected private).each do |scope| 
     if object.send("#{scope}_method_defined?", method_name) 
      return scope 
     end 
     end 
     nil 
    end 
    end 
end 

Thêm một số phương pháp để kiểm tra:

class Foo 
    class << self 
    # This method is private in instance and public in class 
    def twin_method 
     visibility_scope 
    end 

    def class_public_method 
     visibility_scope 
    end 

    protected 

    def class_protected_method 
     visibility_scope 
    end 

    private 

    def class_private_method 
     visibility_scope 
    end 
    end 

    def instance_public_method 
    self.class.visibility_scope 
    end 

    protected 

    def instance_protected_method 
    self.class.visibility_scope 
    end 

    private 

    def twin_method 
    self.class.visibility_scope 
    end 

    def instance_private_method 
    self.class.visibility_scope 
    end 
end 

# singleton methods 
foo = Foo.new 
foo.singleton_class.class_eval do 
    def public_singleton_method 
    Foo.visibility_scope 
    end 

    protected 

    def protected_singleton_method 
    Foo.visibility_scope 
    end 

    private 

    def private_singleton_method 
    Foo.visibility_scope 
    end 
end 

class Bar 
    class << self 
    private 

    def class_private_method 
     Foo.visibility_scope 
    end 
    end 

    protected 

    def instance_protected_method 
    Foo.visibility_scope 
    end 
end 

thử nghiệm

# check ordinary method 
Foo.class_public_method 
=> "public" 
Foo.send(:class_protected_method) 
=> "protected" 
Foo.send(:class_private_method) 
=> "private" 
Foo.new.instance_public_method 
=> "public" 
Foo.new.send(:instance_protected_method) 
=> "protected" 
Foo.new.send(:instance_private_method) 
=> "private" 

# check class and instance methods with the same name 
Foo.twin_method 
=> "public" 
Foo.new.send(:twin_method) 
=> "private" 

# check methods from different objects 
Bar.send(:class_private_method) 
=> "private" 
Bar.new.send(:instance_protected_method) 
=> "protected" 

# check singleton methods 
foo.public_singleton_method 
=> "public" 
foo.send(:protected_singleton_method) 
=> "protected" 
foo.send(:private_singleton_method) 
=> "private" 
+0

Cuối cùng tôi tìm ra cách để thực hiện và thay đổi câu trả lời của mình –

1

Chỉ cần chắc chắn, Tôi đôi kiểm tra với mã ruby, nhưng tôi có thể thiếu một cái gì đó. Tôi không thể tìm thấy bất kỳ cách nào để có phạm vi hiển thị được khai báo hiện tại từ lớp học. Khi nó xuất hiện nếu các phương thức hiển thị (private, public hoặc protected) được khai báo không có đối số tên phương thức, nó sẽ thiết lập phạm vi hiện tại như một khai báo lớp rộng trừ khi chúng ta khai báo phạm vi hiển thị khác trên các câu lệnh tiếp theo.

Bạn có thể kiểm tra mã này để biết thêm điều tra - https://github.com/ruby/ruby/blob/c5c5e96643fd674cc44bf6c4f6edd965aa317c9e/vm_method.c#L1386

tôi không thể tìm thấy bất kỳ phương pháp trực tiếp đề cập đến cref-> VISI, Bạn có thể kiểm tra mã này cho tài liệu tham khảo - https://github.com/ruby/ruby/blob/48cb7391190612c77375f924c1e202178f09f559/eval_intern.h#L236

Dưới đây là cũng là câu trả lời tương tự từ một trong những bài đầu tiên trên Stackoverflow - https://stackoverflow.com/a/28055622/390132

Vì vậy, đây là giải pháp đơn giản, tôi đã đưa ra -

class Foo 
    def self.create_method 
    def example_method 
     "foo" 
    end 

    visibility = if self.private_method_defined? :test_method 
        :private 
       elsif self.public_method_defined? :test_method 
        :public 
       elsif self.protected_method_defined? :test_method 
        :protected 
       end 

    send visibility, :example_method 
    end 

    private 

    # As Ruby doesn't associate visibility flag along with the caller 
    # reference rather with the actual method which are subsequently 
    # declared. So we can take that as an advantage and create a test method 
    # and later from :create_method scope check that particular method 
    # visibility and change the generated method visibility accordingly. 
    # Create a test method to verify the actual visibility when calling 'create_method' method 
    def test_method; end 

    create_method 
end 

puts "Public methods: #{Foo.public_instance_methods(false)}" 
# [] 
puts "Private methods: #{Foo.private_instance_methods(false)}" 
# [:test_method, :example_method] 
puts "Protected methods: #{Foo.protected_instance_methods(false)}" 
# [] 
Các vấn đề liên quan