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

4 Answers 4

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
Sign up to request clarification or add additional context in comments.

Comments

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

2 Comments

This does more than just prevent accidental creation of other instance variables though.
I don't think this would be very helpful. For example: p t.instance_variable_set :@a, 7 # => RuntimeError: can't modify frozen Test
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).

Comments

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?

1 Comment

You can freeze a class, though. And objects, for that matter.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.