From: Benoit Daloze Date: 2010-01-25T00:51:02+09:00 Subject: Re: Writing proper getter in a Ruby way --0016e659f466d98533047deb044c Content-Type: text/plain; charset=ISO-8859-1 > I assume the usual thing is to assign @value = nil in initialize() and > call it a day. That's the first option, it just looks not cool at all, but it's very simple an explicit, sure. > But I think you are asking a higher-level question: How do we abstract > away this detail? > > The attr_* methods define methods which create instance variables upon > first being called. But what if we want those instance variables to be > initialized beforehand? > > We would need to override the attr_* methods. But those methods belong > to the class object--how are they going to initialize variables in the > instances? By inserting code into the initialize() ancestor chain. > > module InitializeAttr > [:attr_reader, :attr_writer, :attr_accessor].each do |method| > define_method method do |*syms| > super(*syms).tap do > mod = Module.new do > define_method :initialize do |*args, &block| > super(*args, &block).tap do > syms.each do |sym| > instance_variable_set("@#{sym}", nil) > end > end > end > end > include mod > end > end > end > end > > class Control > attr_writer :a, :b > def bar > @a.to_i + 5 > end > end > > control = Control.new > p control.instance_variables > #=> [] > p control.bar > #=> warning: instance variable @a not initialized > #=> 5 > > class Experiment > extend InitializeAttr > attr_writer :a, :b > def bar > @a.to_i + 5 > end > end > > exp = Experiment.new > p exp.instance_variables > #=> [:@a, :@b] > p exp.bar > #=> 5 > # yay no warnings > > Therein lies either an abstraction technique or an overkill technique. Cool way to do that :) but overkill also... It's very interesting anyway how we can change that. > A few things about define_method blocks, > > + The &block argument is unsupported in Ruby 1.8.6. Sell your soul to > eval() for a workaround. > > + Implicit super arguments are broken in 1.8.7. It passes the regular > arguments but forgets about the &block argument. > > + In Ruby 1.9 super *must* take explicit arguments. Don't know why. I don't have that: ($ ruby -v #=> ruby 1.9.2dev (2010-01-14 trunk 26319) [x86_64-darwin10.2.0]) class P def m(*args, &b) [args, b] end end class C < P def m(*args, &b) p [args, b] super.tap { |sup| p sup } end end C.new.m(:a, :b) { |e| e } #=> [[:a, :b], #] #=> [[:a, :b], #] #=> [:a, :b] So another way, probably less 'overkill' you made me think is: class Test attr_reader :var def initialize(*args) if args.length > 0 @var = :value end end alias :get_var :var def var get_var || 3 end end p Test.new.var #=> 3 p Test.new(:arg1, :arg2).var #=> :value Just aliasing the old accessor, too easy :) (and short, no line lost if we compare to initialization, and we suppose to have already other attr_*) I just forgot a moment name conflicts about methods don't exist in Ruby, because you can so easily copy(alias) the old method. Thanks for your answer :) --0016e659f466d98533047deb044c--