3

I would like to deny creating instance variables in Ruby,to prevent unattended variables being created 'by mistake'.

My class:

class Test
  def initialize
    @a = 'Var A'
  end

  def make_new
    @b = 'Var B' <-- I would like to deny creation of any variables that were not defined during the init
  end
end
meso_2600
  • 1,940
  • 5
  • 25
  • 50

4 Answers4

4

I don't claim this is a good idea, but just b/c it's kind of interesting, here is a solution that will throw an exception when a new ivar is created, but will also let you modify defined instance variables (unlike freezing the class). Just threw this together, there are undoubtably some issues w/ it, including the fact that it duplicates every method :)

module IvarBlocker
  def method_added(method)
    alias_name = "__#{method}_orig"
    return if method == :initialize || method_defined?(alias_name) || method.match(/__.*_orig/)

    alias_method alias_name, method
    define_method(method) do |*args|
      ivars_before = instance_variables.dup
      send(alias_name, *args).tap { raise "New Ivar assigned" if !(instance_variables - ivars_before).empty? }
    end
  end
end

Usage

class Test
  extend IvarBlocker

  def initialize
    @a = 1
  end

  def set_b
    @b = 2
  end

  def set_a
    @a = 6
  end
end

t = Test.new #=> #<Test:0x007f87f13c41e8 @a=1>
t.set_b #=> RuntimeError: New Ivar assigned
t.set_a #=> 6
Alex.Bullard
  • 5,533
  • 2
  • 25
  • 32
3

You can freeze object instances at the end of initialize method:

class Test
  def initialize
    @a = 'Var A'
    freeze
  end

  def make_new
    @b = 'Var B' # I would like to deny creation of any variables that were not defined during the init
  end
end

t=Test.new
p t.instance_variable_get :@a
# "Var A"
t.make_new
#a.rb:24:in `make_new': can't modify frozen Test (RuntimeError)
#        from a.rb:30:in `<main>'
t.instance_variable_set :@c, 'Var C'
# a.rb:31:in `instance_variable_set': can't modify frozen Test (RuntimeError)
#        from a.rb:31:in `<main>'
class << t
  @d = 'Var D'
end
#a.rb:33:in `singletonclass': can't modify frozen Class (RuntimeError)
#        from a.rb:32:in `<main>'
p t.instance_variable_get :@d
David Unric
  • 7,421
  • 1
  • 37
  • 65
  • 1
    This does more than just prevent accidental creation of other instance variables though. – Max Nov 20 '13 at 23:38
  • 2
    I don't think this would be very helpful. For example: `p t.instance_variable_set :@a, 7 # => RuntimeError: can't modify frozen Test` – Cary Swoveland Nov 21 '13 at 01:56
2

There is a way - a hacky (but fun) way which is not meant for production (and is relatively slow). My sample implementation works for a single object only, but can be extended to support many objects.

Let's assume the following setup:

class Foo
  def initialize
    @a = :foo
  end
  def set_b; @b = 3; end
  def set_c; @c = 7; end
end

def freeze_variables_of(obj)
  frozen_variables = obj.instance_variables
  set_trace_func lambda {|event, file, line, id, binding, classname|
    if classname == obj.class
      this = binding.eval 'self'
      if this == obj
        (this.instance_variables - frozen_variables).each {|var| this.remove_instance_variable var}
      end
    end
  }
end

With the use of set_trace_func we can set a Proc which is called very often (usually more than once per statement). In that Proc we can check instance variables and remove unwanted variables.

Let's look at an example:

a = Foo.new
# => #<Foo:0x007f6f9db75cc8 @a=:foo>

a.set_b; a
# => #<Foo:0x007f6f9db75cc8 @a=:foo, @b=3>

freeze_variables_of a
a.set_c; a
# => #<Foo:0x007f6f9db75cc8 @a=:foo, @b=3>

We see that after doing the "freeze", set_c cannot set the instance variable (in fact the variable is removed at the very moment the set_c method returns).

In contrast to freezing the object (with a.freeze) (which I'd recommend for any real world application), this way allows you to modify all allowed instance variables while forbidding new ones.

This even works, if you directly assign instance variables (while Alex' method is probably faster, but relies on accessor methods).

tessi
  • 13,313
  • 3
  • 38
  • 50
0

There is no way to prevent creation of accidental instance variables defined that way. Why do you want to do this? What would you want such code to achieve?

Max
  • 21,123
  • 5
  • 49
  • 71