Better method for dynamically calling methods from input? - ruby

Is it better to use case/when things or the send method when dynamically calling methods based on user input? "better" based primarily on good coding practices.
input = gets.chomp
case input
when foo
foo
when bar
bar
end
versus
input = gets.chomp #Where hopefully the input would be 'foo' or 'bar'
send(input)

Your wording makes the question incredibly hard to read.
If I understood you correctly, you want to call methods based on user input. One alternative would be to check every possible value and call a method, the other - to use send directly.
First of all, notice that in your first example, you were calling method1 when the user entered foo. If you used send(input) you would have called foo instead. So they are not exactly the same.
You can achieve the same behavior by putting the input->method mapping in a hash like so:
dispatch = {foo: :method1, bar: :method2}
input = gets.chomp.to_sym
send(dispatch[input])
Another thing to note is that send in the original situation would call any method passed. You can instead whitelist the possible methods with the hash above and checking if such value exists:
send(dispatch[input]) if dispatch.key? input
Now to the question of when to use one or the other:
If you have 2, 3, 5 or so possibilities, prefer explicitly listing them. It will be faster, easier to read, easier to do static code analysis and so on.
If you have hundreds and thousands of different methods, prefer send. The costs outweigh the benefits of being DRY.
If the list of allowed methods is generated dynamically, you don't have a choice - use send. Examples:
You want to call methods to a given object and that object is different each time
You want to allow different methods depending on the user's permissions
You want to implement a REPL or some other awesome tool that has extremely dynamic needs
In general, don't use meta programming, unless there is significant gain or you don't have any other choice.

Unless you'd like your user to be able to call any method in the method lookup chain, including private methods which send can invoke, it probably makes sense for you to lock it down and only allow your users some methods.
If you don't specify an object to send to (like in your code above), Ruby will look at self for a method by that name and then use a normal method lookup. In other words self will be the first link in the method lookup chain. If you do specify an object, maybe an object that you create for that purpose for example, another option might be to use the methods like try or respond_to?.

input = gets.chomp
if defined?(input.to_sym)
send(input)
else
puts "No such thing!"

Related

Ruby hash with lazy keys

I have a collection of 'data endpoints'. Each endpoint has a name and can be available or unavailable. In Ruby I want to present the available endpoints as a Hash to make it easy to work with them. The difficulty is that getting information about the endpoints is costly and should be done lazily.
Some examples of how I want my object to behave:
endpoints = get_endpoints.call # No endpoint information is accessed yet
result = endpoints['name1'] # This should only query endpoint "name1"
is_available = endpoints.key? 'name2' # This should only query endpoint "name2"
all_available = endpoints.keys # This has to query all endpoints
The comments describe how the object internally makes requests to the 'data endpoints'.
It is straightforward to make a Hash that can do the first 2 lines. However I don't know how to support the last 2 lines. To do this I need a way to make the keys lazy, not just the values.
Thank you for taking a look!
You'd have to override the key? method, and do your own checking in there.
class LazyHash < Hash
def key?(key)
# Do your checking here. However that looks for your application
end
end
In my opinion, you're asking for trouble though. One of the most powerful virtues in computer science is expectability. If you're changing the behavior of something, modifying it far beyond it's intent, it doesn't serve you to continue calling it by the original name. You don't need to shoe-horn your solution into existing classes/interfaces.
Programming offers you plenty of flexibility, so you can do stuff like this (dependent on the language of course), but in that same argument, you have no reason not to simply build a new object/service with it's own API.
I recommend starting fresh with a new class and building out your desired interface and functionality.
class LazyEndpoints
def on?(name)
end
def set(name, value)
end
end
(Or something like that, the world is yours for the taking!)

ruby : return object if test is true

I'm looking for a function that would do the following:
def self.my_find
object = self.first #Whatever
return object.my_check? ? object : nil
end
Something like check:
object.check(&:my_check?)
It should exist, shouldn't it?
Here's my situation:
In a method (controller), I have some nil-able objects and I need to check something on the object to further use it.
In the event of trying to write pseudo-functional code, the logic is first to retrieve the objects, then making actions on the objects and then return those.
If I have a collection, the code would be:
result = get_my_collection_method.select(&:my_check?).map(&:my_action)
There is no issue if the select methods filters all objects, then result will equal [] but the code is still valid.
I find natural to wanting to do the same even if it is not a collection but a single object:
result = get_my_object.check(&:my_check?).try(:my_action)
But the fact that this method doesn't exist tells me there is no monadic transformation between object and array in ruby.
I guess the simplest way to achieve this is that I transform my singleton into a single value array:
result = Array(get_my_object).select(&:my_check?).map(&:my_action).first
I hope this clarifies why I was looking for such method.
But the question remains: does it exist natively? If I write it myself (even at the Object level), this is not standard, I lose the benefits of writing clean code.
While a method like that might seem useful in your particular circumstance, this sort of thing is generally expressed like:
object = self.first
object.my_check? and object
Where it's understood that this method may return false but that's not a logically true value so it's fine.
If you want to write your own method for Object that does this, you're welcome to, but keep in mind the more esoteric and quirky your code is the harder it will be for other people to engage with. Those "other people" might be you in the future when you've forgotten about your unusual core extensions.
Here's a few ways:
def self.my_find
object = self.first # Whatever
# Use ONE of the following examples:
# 1.
object if object.my_check?
# 2.
object.my_check? && object
# 3.
object.my_check? ? object : nil
end
Note that an explicit return is not needed at the end of a method in ruby.
Depending on the context though (I can only speculate, as none was given!), there might be a cleaner way to do this. For example, if this is actually something you're building in a rails application, then one of these examples would be better:
def self.my_find
where(...).find_by(my_check: true)
# Or, where `my_check` is a *SCOPE*!!
my_check.find_by(...)
end
...With the key difference being that you're performing the check in SQL rather than in ruby. This gives better performance.

Is this use of polymorphism misleading and therefore bad design?

If I have the following ruby module which implements a particular interface (apply in this case)
module FooApplier
def apply
foo
end
end
...and all other "Appliers" are classes, not modules, is it misleading to other engineers to pass FooApplier to receivers which expect the applyinterface?
Let's assume the application using FooApplier runs perfectly fine, but let's also assume that some other engineer didn't take the time to pour over every last byte of my code. If they decide to send something like .new to the FooApplier which somehow induces some subtle bug, is the onus on my design, or the engineer for making assumptions and neglecting to read my code?
The way your module is presented here, it won't work as a stand-in for a class. Let's look at a class first:
class BarApplier
def apply
bar
end
end
apply here is an instance method, so callable on instances of BarApplier, i.e. BarApplier.new.apply. This will not be possible for your module.
Unless of course apply was meant to be a class or module method, in which case your question was misleading since it then should be def self.apply.
But to answer the more general question, in a duck-typed language the sent messages are the interface. In my opinion the caller should make no assumptions about other methods being present. In your specific case, if apply is the only method in the "contract", assuming that the same entity also responds to new is invalid in my opinion.

Extract information about method receiver from stack

If we call caller method, we get something like:
prog.rb:3:in `a'
prog.rb:6:in `b'
prog.rb:9:in `c'
This is helpful for humans, but if I wanted to analyze the stack programmatically, not really, as two methods called :a may be entirely unrelated.
Is there any way/method to extract information about the receiver of the methods (like its class or object id) as well? For example:
prog.rb:3:in `Klass#a'
prog.rb:6:in `Modoole#b'
prog.rb:9:in `OtherKlass#c'
Formatting is only an example; this info might be an Array or anything.
I'm trying to emulate this with TracePoint, but forming a separate stack is a bad solution. Is there any Ruby way I missed in the docs?
There's an alternative to Kernel#caller named Kernel#caller_locations, that returns an array of Thread::Backtrace::Location objects. According to the manual, these should in theory be able to give you this information through the #label method.
Returns the label of this frame.
Usually consists of method, class, module, etc names with decoration.
After trying this out however, I need to question the term usually in the docs, because it seems to only return the method name still. Unless usually means it works for you, there seems to be no way of accomplishing this as of now.
Edit:
As per comment, one case that satisfies the condition of usually is when the method call is coming from within a Class or Module body:
class A
def trace
puts caller_locations.first.label
end
end
class B
A.new.trace
end
#=> <class:B>
module C
A.new.trace
end
#=> <module:C>

Ruby nested send

Say I have an object with a method that accesses an object:
def foo
#foo
end
I know I can use send to access that method:
obj.send("foo") # Returns #foo
Is there a straightforward way to do a recursive send to get a parameter on the #foo object, like:
obj.send("foo.bar") # Returns #foo.bar
You can use instance_eval:
obj.instance_eval("foo.bar")
You can even access the instance variable directly:
obj.instance_eval("#foo.bar")
While OP has already accepted an answer using instance_eval(string), I would strongly urge OP to avoid string forms of eval unless absolutely necessary. Eval invokes the ruby compiler -- it's expensive to compute and dangerous to use as it opens a vector for code injection attacks.
As stated there's no need for send at all:
obj.foo.bar
If indeed the names of foo and bar are coming from some non-static calculation, then
obj.send(foo_method).send(bar_method)
is simple and all one needs for this.
If the methods are coming in the form of a dotted string, one can use split and inject to chain the methods:
'foo.bar'.split('.').inject(obj, :send)
Clarifying in response to comments: String eval is one of the riskiest things one can do from a security perspective. If there's any way the string is constructed from user supplied input without incredibly diligent inspection and validation of that input, you should just consider your system owned.
send(method) where method is obtained from user input has risks too, but there's a more limited attack vector. Your user input can cause you to execute any 0-arghument method dispatchable through the receiver. Good practise here would be to always whitelist the methods before dispatching:
VALID_USER_METHODS = %w{foo bar baz}
def safe_send(method)
raise ArgumentError, "#{method} not allowed" unless VALID_USER_METHODS.include?(method.to_s)
send(method)
end
A bit late to the party, but I had to do something similar that had to combine both 'sending' and accessing data from a hash/array in a single call. Basically this allows you to do something like the following
value = obj.send_nested("data.foo['bar'].id")
and under the hood this will do something akin to
obj.send(data).send(foo)['bar'].send(id)
This also works with symbols in the attribute string
value = obj.send_nested('data.foo[:bar][0].id')
which will do something akin to
obj.send(data).send(foo)[:bar][0].send(id)
In the event that you want to use indifferent access you can add that as a parameter as well. E.g.
value = obj.send_nested('data.foo[:bar][0].id', with_indifferent_access: true)
Since it's a bit more involved, here is the link to the gist that you can use to add that method to the base Ruby Object. (It also includes the tests so that you can see how it works)

Resources