Checking Class or Module Membership






Checking Class or Module Membership

Problem

You want to see if an object is of the right type for your purposes.

Solution

If you plan to call a specific method on the object, just check to see whether the object reponds to that method:

	def send_as_package(obj)
	  if obj.respond_to? :package
	    packaged = obj.package

	  else
	    $stderr.puts "Not sure how to package a #{obj.class}."
	    $stderr.puts 'Trying generic packager.'
	    package = Package.new(obj)
	  end
	  send(package)
	end

If you really can only accept objects of one specific class, or objects that include one specific module, use the is_a? predicate:

	def multiply_precisely(a, b)
	  if a.is_a? Float or b.is_a? Float

	    raise ArgumentError, "I can't do precise multiplication with floats."
	  end
	  a * b
	end

	multiply_precisely(4, 5) # => 20
	multiply_precisely(4.0, 5)
	# ArgumentError: I can't do precise multiplication with floats.

Discussion

Whenever possible, you should use duck typing (Object#respond_to?) in preference to class typing (Object#is_a?). Duck typing is one of the great strengths of Ruby, but it only works if everyone uses it. If you write a method that only accepts strings, instead of accepting anything that supports to_str, then you've broken the duck typing illusion for everyone who uses your code.

Sometimes you can't use duck typing, though, or sometimes you need to combine it with class typing. Sometimes two different classes define the same method (especially one of the operators) in completely different ways. Duck typing makes it possible to silently do the right thing, but if you know that duck typing would silently do the wrong thing, a little class typing won't hurt.

Here's a method that uses duck typing to see whether an operation is supported, and class typing to cut short a possible problem before it occurs:

	def append_to_self(x)
	  unless x.respond_to? :<<
	    raise ArgumentError, "This object doesn't support the left-shift operator."
	  end
	  if x.is_a? Numeric
	    raise ArgumentError,
	    "The left-shift operator for this object doesn't do an append."
	  end
	  x << x
	end

	append_to_self('abc')                             # => "abcabc"
	append_to_self([1, 2, 3])                         # => [1, 2, 3, […]]

	append_to_self({1 => 2})
	# ArgumentError: This object doesn't support the left-shift operator.

	append_to_self(5)
	# ArgumentError: The left-shift operator for this object doesn't do an append.
	5 << 5                                      # => 160
	# That is, 5 * (2 ** 5)

An alternative solution approximates the functionality of Java's interfaces. You can create a dummy module for a given capability, have all appropriate classes include it, and use is_a? to check for inclusion of the module. This requires that each participating class signal its ability to perform a certain task, but it doesn't tie you to any particular class hierarchy, and it saves you from calling the wrong method just because it has the right name.

	module ShiftMeansAppend
	  def <<(x)
	  end
	end

	class String
	  include ShiftMeansAppend
	end

	class Array
	  include ShiftMeansAppend
	end

	def append_to_self(x)
	  unless x.is_a? ShiftMeansAppend
	    raise ArgumentError, "I can't trust this object's left-shift operator."
	  end
	  x << x
	end
	append_to_self 4
	# ArgumentError: I can't trust this object's left-shift operator.

	append_to_self '4'                         # => "44"

See Also

  • Recipe 1.12, "Testing Whether an Object Is String-Like"



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