Ruby: having callbacks on 'attr' objects
Essentially I'm wondering how to place callbacks on objects in ruby, so that when an object is changed in anyway I can automatically trigger other changes:
(EDIT: I confused myself in my own example! Not a good sign… As @proxy is a URI object it has it's own methods, changing the URI object by using it's own methods doesn't call my own proxy=
method and update the @http object)
class MyClass
attr_reader :proxy
def proxy=(string_proxy = "")
begin
@proxy = URI.parse("http://"+((string_proxy.empty?) ? ENV['HTTP_PROXY'] : string_proxy))
@http = Net::HTTP::Proxy.new(@proxy.host,@proxy.port)
rescue
@http = Net::HTTP
end
end
end
m = MyClass.new
m.proxy = "myproxy.com:8080"
p m.proxy
# => <URI: @host="myproxy.com" @port=8080>
m.proxy.host = 'otherproxy.com'
p m.proxy
# => <URI: @host="otherproxy.com" @port=8080>
# But accessing a website with @http.get('http://google.com') will still travel through myproxy.com as the @http object hasn't been changed when开发者_开发知识库 m.proxy.host was.
Your line m.proxy = nil
will raise a NoMethodError
exception, since nil
does no respond to empty?
. Thus @http
is set to Net::HTTP
, as in the rescue clause.
This has nothing to do with callbacks/setters. You should modify your code to do what you want (e.g. calling string_proxy.blank?
if using activesupport).
I managed to figure this one out for myself!
# Unobtrusive modifications to the Class class.
class Class
# Pass a block to attr_reader and the block will be evaluated in the context of the class instance before
# the instance variable is returned.
def attr_reader(*params,&block)
if block_given?
params.each do |sym|
# Create the reader method
define_method(sym) do
# Force the block to execute before we…
self.instance_eval(&block)
# … return the instance variable
self.instance_variable_get("@#{sym}")
end
end
else # Keep the original function of attr_reader
params.each do |sym|
attr sym
end
end
end
end
If you add that code somewhere it'll extend the attr_reader
method so that if you now do the following:
attr_reader :special_attr { p "This happens before I give you @special_attr" }
It'll trigger the block before it gives you the @special_attr
. It's executed in the instance scope so you can use it, for example, in classes where attributes are downloaded from the internet. If you define a method like get_details
which does all retrieval and sets @details_retrieved
to true
then you can define the attr like this:
attr_reader :name, :address, :blah { get_details if @details_retrieved.nil? }
精彩评论