Does Ruby have a method_missing equivalent for undefined instance variables?
Asked Answered
V

4

13

When I invoke a method that doesn't exist, method_missing will tell me the name of the method. When I attempt to access a variable that hasn't been set, the value is simply nil.

I'm attempting to dynamically intercept access to nil instance variables and return a value based on the name of the variable being accessed. The closest equivalent would be PHP's __get. Is there any equivalent functionality in Ruby?

Valued answered 4/10, 2011 at 16:40 Comment(1)
Do you know about warnings and how to turn them on?Sharronsharyl
P
2

I do not believe this is possible in Ruby. The recommended way would be to use a ''user'' method rather than a ''@user'' instance var in your templates.

This is consistent with the way you deal with Ruby objects externally (''obj.user'' is a method which refers to ''@user'', but is actually not ''@user'' itself). If you need any kind of special logic with an attribute, your best bet is to use a method (or method_missing), regardless if you're accessing it from inside or outside the object.

Pagel answered 4/10, 2011 at 20:57 Comment(0)
M
2

See my answer to another similar question. But just because you can do it doesn't mean that it's a good idea. Sensible design can generally overcome the need for this kind of thing and allow you to produce more readable and hence maintainable code.

instance_variable_get seems to be the closest equivalent of PHP's __get from what I can see (although I'm not a PHP user).

Looking at the relevant Ruby source code, the only 'missing' method for variables is const_missing for constants, nothing for instance variables.

Mutualism answered 6/10, 2011 at 8:39 Comment(2)
Doesn't help. I would have to turn all of my instance variable uses into method calls, which is completely impractical.Valued
Well there is no such facility in Ruby for accessing instance variables dynamically other than what I described in that other answer. See updated answer above.Mutualism
S
0

there isn't an instance_variable_missing (at least that I know of) But why are you accessing randomly named instance variables anyway?

If your thread all the access to the object state through method calls (as you should anyway) then you wouldn't need this.

If you are looking for a way to define magic stuff without messing up with the method lookup, you may want to use const_missing.

Skyros answered 4/10, 2011 at 16:50 Comment(2)
The variables aren't randomly named. I'm playing around with rendering an email without going through the mailer responsible for setting up instance variables, for testing purposes. The view will attempt to access things like @user.name without @user being set. I thought I might be able to dynamically generate FactoryGirl objects for things like @user on first read.Valued
I see thanks. AFAICT I'm afraid your only option is to read the template file and set the vars accordingly (or possibly turn the problem around and use method_missing in your email template). Which is a shame since ruby even knows when you are accessing an undefined variable and will give you a warning.Skyros
B
-1

A bit late but, instance_variable_missing is the same as method_missing to a point... Take the following class:

class Test
 def method_missing(*args)
  puts args.inspect
 end
end 
t = Test.new

Now let's get some instance variables:

t.pineapples     #=> [:pineapples]
t.pineapples = 5 #=> [:pineapples=,5]

Not sure why the method is nil for you...

EDIT:

By the sounds of it you want to accomplish:

t = SomeClass.new
t.property.child = 1

So let's try returning a Test object from our previous example:

class Test
 def method_missing(*args)
  puts args.inspect
  return Test.new
 end
end 

So what happens when we call:

t = Test.new
t.property.child = 1
#=>[:property]
#=>[:child=,1]

So this goes to show that this is indeed possible to do. OpenStruct uses this same technique to set instance variables dynamically. In the below example, I create EternalStruct which does exactly what you wanted:

require 'ostruct'
class EternalStruct < OpenStruct
  def method_missing(*args)
    ret = super(*args)
    if !ret
      newES = EternalStruct.new
      self.__send__((args[0].to_s + "=").to_sym, newES)
      return newES
    end
  end
end

Usage of EternalStruct:

t = EternalStruct.new
t.foo.bar.baz = "Store me!"
t.foo.bar.baz #=> "Store me!"
t.foo #=> #<EternalStruct bar=#<EternalStruct baz="Store me!">>
t.a = 1
t.a #=> 1
t.b #=> #<EternalStruct:...>
t.b = {}
t.b #=> {}
def t.c(arg)
  puts arg
end
t.c("hi there") #=> "hi there"
Bora answered 23/6, 2018 at 22:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.