0

I really like Ruby but I'm confused over how exactly I should implement an interface. In the example shown below, a class A calls a method get_data on an object that is passed in as a constructor argument. What is the best practice for ensuring that obj is of the correct type and implements the method get_data? I have seen code that simply "specifies the required interface" in the RDoc for the class's initialize method: "Argument must include module Bar", "Argument must be of type Blah" or "Argument must have the method 'get_data'". Is there a better programmatic way of doing this?

class A

   def initialize(obj)
     @obj = obj
   end

   def foo
     # Do something with @obj
     var = @obj.get_data
     ...
   end

end
Boon
  • 1,073
  • 1
  • 16
  • 42

2 Answers2

1

I think what you are looking at is a Dependency injection, instead of hard coding SomeClass.new.get_data in your method foo

It's a level of trust that class A has, that an object initializing A should pass an argument, which is an object of SomeClass like this.

object_a = A.new(SomeClass.new).

So if you want to check explicitly if @obj can can respond to the message get_data you can use ruby's respond_to?(:method_name)

So your code becomes this

   def foo
     # Do something with @obj
     if @obj.respond_to?(:get_data)
       var = @obj.get_data
     end
     ...
   end
gates
  • 4,465
  • 7
  • 32
  • 60
-1

Ruby is a dynamically typed language and it's also duck-typed, when you decide to use duck-typing features you should probably just mention it in documentation and call it, if it's not there it will crash, think about it carefully, pretend you could check that get_data exists(and you could as we shall see later), what do you want to do ?

Do you want to return nil ? if foo should return a value you could return nil but you'll risk your users a NoSuchMethodError for nil:NilClass, if foo is meant to return nothing(semantically void) then nil is no option to you, do you want to raise an Error ? well probably the very reason you want to check that get_data exists is to protect against type errors but now you raise another error, maybe you want to raise a specific error but honestly NoSuchMethodError : method get_data missing in someObject:SomeClass is specific enough in my opinion.

With that in my mind I'm going to show you how to check for it anyway, there are many ways, a very simple way :

def foo
   begin
      var = @obj.get_data
   rescue NoMethodError
      // handle the case where get_data does not exist
   end
end

A probably better way is to check that obj has get_data in initialize using the above way or this :

def initialize(obj)
    if(!obj.respond_to? :get_data)
       //handle the case where get_data is not defined
    end
end

The problem with the last code is that all you check is obj having get_data, you don't check whether it has proper signature, if you want to check that you can :

def initialize(obj)
  unless(!obj.respond_to? :get_data) 
     params = obj.method(:get_data).parameters
     //if get_data for example takes x,y then params will be `[[:req,:x],[:req,:y]]`         
  end
end

Of course you can't check that get_data accepts specific types because ruby is dynamically typed.

If you also want to check the method's return type after you check for its existence and parameters you can call it because it's safe and check the return type like this :

a=obj.get_data
if(a.is_a? SomeClass)
   //good
end

If you want to check that get_data returns nothing(semantically void method), well in ruby returning nil is analogous to void but so is the failure to return a value so you can't check for that.

niceman
  • 2,653
  • 29
  • 57
  • There is no pre-defined constant `NoSuchMethodError` in Ruby. Do you mean `NoMethodError`? – Jörg W Mittag Jun 14 '17 at 12:41
  • `method_defined?` checks whether a method is defined in a `Module`. Presumably, `obj` is not a `Module`, so `obj.method_defined?` will simply `raise` a `NoMethodError`. And even if `obj` *were* a `Module`, then the fact that the method is defined in that module, does not mean that it can be called on `obj`, it only means that it can be called on *instances* of `obj`. Presumably, you want `obj.method(:get_data).nil?` or something like that. But even that will fail, because it is perfectly possible for an object to respond to a message without having a corresponding method. – Jörg W Mittag Jun 14 '17 at 12:43
  • @JörgWMittag fixed, thanks , what do you mean by "it is perfectly possible for an object to respond to a message without having a corresponding method" ? are you talking about the case where `get_data` is defined on `obj` but not on `obj`'s class ? – niceman Jun 14 '17 at 15:07
  • `class Foo; def method_missing(meth, *args) puts "Echoing #{meth} with (#{args.join(' ')})" end; def respond_to_missing?(*) true end end; foo = Foo.new; foo.hello(42); foo.method(:hello).nil? == true; foo.respond_to?(:hello) == true`. – Jörg W Mittag Jun 14 '17 at 23:20