Setting attr_reader for lazy attribute creation

(Great editing, I got part of the way ...) I was hacked and I came up with this to indicate what needs to be done before reading the attributes:

class Class def attr_reader(*params) if block_given? params.each do |sym| define_method(sym) do yield self.instance_variable_get("@#{sym}") end end else params.each do |sym| attr sym end end end end class Test attr_reader :normal attr_reader(:jp,:nope) { changethings if @nope.nil? } def initialize @normal = "Normal" @jp = "JP" @done = false end def changethings p "doing" @jp = "Haha!" @nope = "poop" end end j = Test.new p j.normal p j.jp 

But changethings not recognized as a method - who has any ideas?

+4
source share
3 answers

You need to evaluate the block in the context of the instance. yield will evaluate it in its native context by default.

 class Class def attr_reader(*params, &blk) if block_given? params.each do |sym| define_method(sym) do self.instance_eval(&blk) self.instance_variable_get("@#{sym}") end end else params.each do |sym| attr sym end end end end 
+3
source

Here is another alternative approach that you can look at. This is not as elegant as what you are trying to do using define_method , but it might be worth a look.

Add new lazy_attr_reader method to Class

 class Class def lazy_attr_reader(*vars) options = vars.last.is_a?(::Hash) ? vars.pop : {} # get the name of the method that will populate the attribute from options # default to 'get_things' init_method = options[:via] || 'get_things' vars.each do |var| class_eval("def #{var}; #{init_method} if !defined? @#{var}; @#{var}; end") end end end 

Then use it as follows:

 class Test lazy_attr_reader :name, :via => "name_loader" def name_loader @name = "Bob" end end 

In action:

 irb(main):145:0> t = Test.new => #<Test:0x2d6291c> irb(main):146:0> t.name => "Bob" 
+1
source

IMHO the change in the context of the block is quite controversial, from the point of view of the person who will use such attr_reader for steroids.

Maybe you should think about how to simply specify the method name using optional arguments:

 def lazy_attr_reader(*args, params) args.each do |e| define_method(e) do send(params[:init]) if params[:init] && !instance_variable_get("@#{e}") instance_variable_get("@#{e}") end end end class Foo lazy_attr_reader :foo, :bar, :init => :load def load @foo = 'foo' @bar = 'bar' end end f = Foo.new puts f.bar #=> bar 
+1
source

Source: https://habr.com/ru/post/1315682/


All Articles