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
Strings
Numbers
Date and Time
Arrays
Hashes
Files and Directories
Code Blocks and Iteration
Objects and Classes8
Modules and Namespaces
Reflection and Metaprogramming
XML and HTML
Graphics and Other File Formats
Databases and Persistence
Internet Services
Web Development Ruby on Rails
Web Services and Distributed Programming
Testing, Debugging, Optimizing, and Documenting
Packaging and Distributing Software
Automating Tasks with Rake
Multitasking and Multithreading
User Interface
Extending Ruby with Other Languages
System Administration