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
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
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.
Related
Example:
7.cars.2.doors
I will add the cars method to Integer and it will return a CarBuilding object.
This CarBuilding object then needs to receive the message 2 which will do some processing and return itself.
Finally doors will be called on this CarBuilding object
Any kind of fancy Meta Programming able to do this?
Note: I don't want to pass parameters. Just use method chaining.
No, this isn't possible.
Using normal message sending syntax, the message must be a valid identifier. 2 is not a valid identifier, identifiers can't start with a digit. This is simply not syntactically legal.
It is possible to define a method named 2 using metaprogramming. Note, however, that this is the name 2 and not the Integer 2. Obviously, it is not possible to invoke such a method using normal message sending syntax (nor is it possible to define it using normal method definition syntax).
class CarBuilding
define_method(:'2') do
CarBuildingWithDoors.new(2)
end
end
7.cars.public_send(:'2').doors
Because names started with digit are not valid you can use underscore:
class CarBuilding
def method_missing(method, *args, &block)
raise NoMethodError unless method =~ /\A_\d*\z/
puts "do something with #{method[1..-1].to_i}"
return self
end
def doors
puts "doors"
end
end
class Integer
def cars
return CarBuilding.new
end
end
puts 7.cars._2.doors
When trying to use an instance method of a Ruby-C-Class:
RubyCClass.new.someMethod()
Ruby is raising the following error:
Error: wrong argument type RubyCClass (expected Data)
Is there any way I can instantiate the class properly such that RubyCClass is instantiated to the extent that someMethod will begin execution? In other words, is there a way I can inject Data into RubyCClass such that someMethod begins execution?
I'm not sure where that error is being generated; is it when the engine is evaluating the value returned by your Ruby code?
If so, you could do whatever you want to do, and then return a dummy Data object:
RubyCClass.new.someMethod()
# do other things, then:
Data.new
# or whatever it is you do to create a Data instance;
# as the final value in your code it will be returned
[Note: This answer was posted when the question was drastically different; it has been edited since then.]
I'm not completely sure if your question, but I think your main problem as that you are using method instead of public_send. (And, by the way, you can get a list of an object's public methods by calling object.public_methods, in case that's helpful.)
Here is some code that illustrates what might work for you:
#!/usr/bin/env ruby
class MethodAccessibility
attr_reader :accessibles, :inaccessibles
def initialize
#accessibles = []
#inaccessibles = []
populate_data
end
def method_accessible?(object, method_name, *args)
begin
object.public_send(method_name, args)
true
rescue Exception => e
e.to_s != "Error: This method cannot be used within the User Interface"
end
end
def add_to_appropriate_array(object, method_name, *args)
accessible = method_accessible?(object, method_name, args)
(accessible ? accessibles : inaccessibles) << method_name
end
def populate_data
object = # create the object on which to call the methods
add_to_appropriate_array(object, :method1, [:arg1, :arg2]) # for examples
add_to_appropriate_array(object, :method2, [])
# ...
end
end
ma = MethodAccessibility.new
ma.accessibles # do something with this array, or the `inaccessibles` array
I am trying to write this inside my class:
class << self
def steps
#steps.call
end
def transitions
#transitions.call
end
def steps(&steps)
#steps = steps
end
def transitions(&transitions)
#transitions = transitions
end
end
That won't work since in Ruby, I can't do this kind of method overloading. Is there a way around this?
You can kind of do this with method aliasing and mixins, but the way you handle methods with different signatures in Ruby is with optional arguments:
def steps(&block)
block.present? ? #steps = block : #steps.call
end
This sort of delegation is a code smell, though. It usually means there's something awkward about the interface you've designed. In this case, something like this is probably better:
def steps
#steps.call
end
def steps=(&block)
#steps = block
end
This makes it clear to other objects in the system how to use this interface since it follows convention. It also allows for other cases, like passing a block into the steps method for some other use:
def steps(&block)
#steps.call(&block)
end
Ruby does not support method overloading (see "Why doesn't ruby support method overloading?" for the reason). You can, however, do something like:
def run(args*)
puts args
end
args will then be an array of the arguments passed in.
You can also pass in a hash of options to handle arguments, or you can pass in nil when you don't want to supply arguments and handle nil in your method body.
Given a class:
class Foo
def initialize(input1)
#input1 = input1
end
end
is there some way that would throw a more helpful error against a = Foo.new()? How can I build a method that throws an ArgumentError in a more helpful way?
I'd like to build this into the class. The Programming Ruby site lists several error-trapping mechanisms, but all of these seem to depend on wrapping a = Foo.new() in a catch block or the like. I would like to have my error trapping within the class itself however.
Since you're new to Ruby it's understandable this error might seem odd, yet it's also an error that's very specific to passing the wrong arguments in. Remapping it to something else isn't necessarily helpful, it ends up hiding problems in your code. I'd suggest leaving it as-is and expecting errors like that to occur if you're not calling it correctly.
The alternative is, at least in newer versions of Ruby, to declare keyword arguments with no defaults:
def initialize(input1:)
end
That's a required keyword argument, and the error is more specific:
ArgumentError: missing keyword: input1
The downside is you have to call it like this:
Foo.new(input1: 'test')
That might be beneficial in terms of clarity. It's up to you.
you can use a default value and raise whatever error you need within the initialize method for example
Class A
def initialize(a = nil)
raise("give me an A") if a.nil?
#a = a
end
end
You can do this pretty simply by raising that error when the argument is not defined. You can add a message to the ArgumentError exception by specifying it as an argument on the exception:
class Foo
def initialize(input1=nil)
raise ArgumentError, "expected a value for Foo.new('value')" unless input1
#input1 = input1
end
end
After reading Programming Ruby a bit more, I think using alias_method as a hook might serve:
alias_method :initialize_orig, :initialize
def initialize(*args)
begin
result = initialize_orig(*args)
return result
rescue Exception
$stderr.print "Need to use argument 'input1'\n"
raise
end
end
I understand that method_missing is something of a last resort when Ruby is processing messages. My understanding is that it goes up the Object hierarchy looking for a declared method matching the symbol, then back down looking for the lowest declared method_missing. This is much slower than a standard method call.
Is it possible to intercept sent messages before this point? I tried overriding send, and this works when the call to send is explicit, but not when it is implicit.
Not that I know of.
The most performant bet is usually to use method_missing to dynamically add the method being to a called to the class so that the overhead is only ever incurred once. From then on it calls the method like any other method.
Such as:
class Foo
def method_missing(name, str)
# log something out when we call method_missing so we know it only happens once
puts "Defining method named: #{name}"
# Define the new instance method
self.class.class_eval <<-CODE
def #{name}(arg1)
puts 'you passed in: ' + arg1.to_s
end
CODE
# Run the instance method we just created to return the value on this first run
send name, str
end
end
# See if it works
f = Foo.new
f.echo_string 'wtf'
f.echo_string 'hello'
f.echo_string 'yay!'
Which spits out this when run:
Defining method named: echo_string
you passed in: wtf
you passed in: hello
you passed in: yay!