Checking Whether an Object Has Necessary Attributes






Checking Whether an Object Has Necessary Attributes

Problem

You're writing a class or module that delegates the creation of some of its instance variables to a hook method. You want to be make sure that the hook method actually created those instance variables.

Solution

Use the Object#instance_variables method to get a list of the instance variables. Check them over to make sure all the necessary instance variables have been defined. This Object#must_have_instance_variables method can be called at any time:

	class Object
	  def must_have_instance_variables(*args)
	    vars = instance_variables.inject({}) { |h,var| h[var] = true; h }
	    args.each do |var|
	      unless vars[var]
	        raise ArgumentError, %{Instance variable "@#{var} not defined"}
	      end
	    end
	  end
	end

The best place to call this method is in initialize or some other setup method of a module. Alternatively, you could accept values for the instance variables as arguments to the setup method:

	module LightEmitting
	  def LightEmitting_setup
	    must_have_instance_variables :light_color, :light_intensity
	    @on = false
	  end

	  # Methods that use @light_color and @light_intensity follow…
	end

You can call this method from a class that defines a virtual setup method, to make sure that subclasses actually use the setup method correctly:

	class Request
	  def initialize
	    gather_parameters # This is a virtual method defined by subclasses
	    must_have_instance_variables :action, :user, :authentication
	  end

	  # Methods that use @action, @user, and @authentication follow…
	end

Discussion

Although Object#must_have_instance_variables is defined and called like any other method, it's conceptually a "decorator" method similar to attr_accessor and private. That's why I didn't use parentheses above, even though I called it with multiple arguments. The lack of parentheses acts as a visual indicator that you're calling a decorator method, one that alters or inspects a class or object.

Here's a similar method that you can use from outside the object. It basically implements a batch form of duck typing: instead of checking an object's instance variables (which are only available inside the object), it checks whether the object supports all of the methods you need to call on it. It's useful for checking from the outside whether an object is the "shape" you expect.

	class Object
	  def must_support(*args)
	    args.each do |method|
	      unless respond_to? method
	        raise ArgumentError, %{Must support "#{method}"}
	      end
	    end
	  end
	end

	obj = "a string"
	obj.must_support :to_s, :size, "+".to_sym
	obj.must_support "+".to_sym, "-".to_sym
	# ArgumentError: Must support "-"

See Also

  • Recipe 10.16, "Enforcing Software Contracts"



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows