ruby hash with methods on pair - ruby

I'm using a rake task to receive and handle data.
The data looks like "code:value" where each code maps to a specific action.
For example, "0xFE:0x47" calls the method corresponding to the 0xFE tag with the parameter 0x47.
For scalability purposes I think this should be mapped to an hash and have the methods defined below:
tags = Hash[0xFA => taskA, 0xFB => taskB, 0xFC => taskC]
def taskA(value)
...
end
def taskB(value)
...
end
def taskC(value)
...
end
then, when a message is received, do a split and call the method on the hash, like:
tokens = message.split(':')
tags[tokens[0]](tokens[1])
Ruby doesn't like the Hash initialization. What's the correct way to solve this problem?

Maybe you're expecting the methods to work like they do in JavaScript, where they're just references until called, but this is not the case. The best approach is to keep them as symbols and then use the send method to call them:
# Define a mapping table between token and method to call
tags = {
0xFA => :taskA,
0xFB => :taskB,
0xFC => :taskC
}
tokens = message.split(/:/)
# Call the method and pass through the value
send(tags[tokens[0]], tokens[1])
The Hash[] initializer is usually reserved for special cases, such as when converting an Array into a Hash. In this case it's redundant if not confusing so is best omitted. { ... } has the effect of creating a Hash implicitly.

Define "doesn't like".
If you try to initialize the hash using function names:
Before the function is defined, you'll get an undefined symbol.
After the function is defined, you're just executing the function.
If you want to map values to functions you can call, consider defining the methods as Procs/lambdas:
> taskA = lambda { |value| puts "foo #{value}" }
> h = { 0x42 => taskA }
> h[0x42].call("bar")
foo bar
You could store symbols, too, but I prefer to use known-existing artifacts, like a variable, so an IDE can help me make sure I'm doing things as right as it can know–symbols are arbitrary and there's no way to make sure they align with an existing method other than trying to call it.

Related

How do I write an rspec test if an array of hashes has a attribute/value pair

Given an array of hashes I want to check if each one contains a certain key and value. The following did NOT work:
it { expect(some_array).to all( have_attributes(:some_key => 'some_value') ) }
I could not tell from the match error why it didn't work but I think it's something to do with expectations have_attributes has about the input arguments or environment.
Make a custom matcher as follows:
RSpec::Matchers.define :have_member_with_value do |expected_key, expected_value|
match do |actual|
actual[expected_key] == expected_value
end
end
Usage:
it { expect(some_array).to all( have_member_with_value(:some_key, "some_value") ) }
Sadly I'm not sure why the approach in the question does not work.
I think the assertion does not work because have_attributes does not work with plain ruby hash keys. You can't access hash keys the same as attributes if you're using a vanilla Ruby hash.
Consider:
a = OpenStruct.new(hello: 'you')
b = { hello: 'you' }
a.hello # this is an attribute automatically defined via OpenStruct
=> "you"
b.hello # this is a regular ol' key
NoMethodError: undefined method `hello' for {:hello=>"you"}:Hash
from (pry):79:in `<main>'
I believe the matcher would work if the object you were working with had the attribute accessor for whatever key-value you were looking for. Ex. If you had an array of OpenStructs, using both match_array and have_attributes would work. These are usually available automatically via metaprogramming if you're using a fancy library like ActiveRecord or OpenStruct.
Otherwise, you have to define these attributes yourself, or assert on the hash key rather than the attribute.
I would probably do something like this:
it do
expect(subject.body.map { |elem| elem[:some_key] }).to all( eq "some_value" ) }
end
I would loop through subject.body and write the expectation within the loop
e.g
subject.body.each do |entry|
it { expect(entry[:some_key]).to eq "some_value"}
end

Store functions in hash

I know that in Ruby you can create hash maps like this:
hash = {"name"=>"John"}
But is it possible to have a hash map of methods:
hash = {"v"=> show_version}
and when calling hash["v"] either to execute the show_version function, or to return some kind on pointer that passed to some special method, to execute the function from the hash?
What I am trying to achieve, is to have a hash map of methods, instead of using a case/when construction, as it looks too verbose to me.
Not exactly like this, no. You need to get a hold of the Method proxy object for the method and store that in the Hash:
hash = { 'v' => method(:show_version) }
And you need to call the Method object:
hash['v'].()
Method duck-types Proc, so you could even store simple Procs alongside Methods in the Hash and there would be no need to distinguish between them, because both of them are called the same way:
hash['h'] = -> { puts 'Hello' }
hash['h'].()
# Hello

undefined method `assoc' for #<Hash:0x10f591518> (NoMethodError)

I'm trying to return a list of values based on user defined arguments, from hashes defined in the local environment.
def my_method *args
#initialize accumulator
accumulator = Hash.new(0)
#define hashes in local environment
foo=Hash["key1"=>["var1","var2"],"key2"=>["var3","var4","var5"]]
bar=Hash["key3"=>["var6"],"key4"=>["var7","var8","var9"],"key5"=>["var10","var11","var12"]]
baz=Hash["key6"=>["var13","var14","var15","var16"]]
#iterate over args and build accumulator
args.each do |x|
if foo.has_key?(x)
accumulator=foo.assoc(x)
elsif bar.has_key?(x)
accumulator=bar.assoc(x)
elsif baz.has_key?(x)
accumulator=baz.assoc(x)
else
puts "invalid input"
end
end
#convert accumulator to list, and return value
return accumulator = accumulator.to_a {|k,v| [k].product(v).flatten}
end
The user is to call the method with arguments that are keywords, and the function to return a list of values associated with each keyword received.
For instance
> my_method(key5,key6,key1)
=> ["var10","var11","var12","var13","var14","var15","var16","var1","var2"]
The output can be in any order. I received the following error when I tried to run the code:
undefined method `assoc' for #<Hash:0x10f591518> (NoMethodError)
Please would you point me how to troubleshoot this? In Terminal assoc performs exactly how I expect it to:
> foo.assoc("key1")
=> ["var1","var2"]
I'm guessing you're coming to Ruby from some other language, as there is a lot of unnecessary cruft in this method. Furthermore, it won't return what you expect for a variety of reasons.
`accumulator = Hash.new(0)`
This is unnecessary, as (1), you're expecting an array to be returned, and (2), you don't need to pre-initialize variables in ruby.
The Hash[...] syntax is unconventional in this context, and is typically used to convert some other enumerable (usually an array) into a hash, as in Hash[1,2,3,4] #=> { 1 => 2, 3 => 4}. When you're defining a hash, you can just use the curly brackets { ... }.
For every iteration of args, you're assigning accumulator to the result of the hash lookup instead of accumulating values (which, based on your example output, is what you need to do). Instead, you should be looking at various array concatenation methods like push, +=, <<, etc.
As it looks like you don't need the keys in the result, assoc is probably overkill. You would be better served with fetch or simple bracket lookup (hash[key]).
Finally, while you can call any method in Ruby with a block, as you've done with to_a, unless the method specifically yields a value to the block, Ruby will ignore it, so [k].product(v).flatten isn't actually doing anything.
I don't mean to be too critical - Ruby's syntax is extremely flexible but also relatively compact compared to other languages, which means it's easy to take it too far and end up with hard to understand and hard to maintain methods.
There is another side effect of how your method is constructed wherein the accumulator will only collect the values from the first hash that has a particular key, even if more than one hash has that key. Since I don't know if that's intentional or not, I'll preserve this functionality.
Here is a version of your method that returns what you expect:
def my_method(*args)
foo = { "key1"=>["var1","var2"],"key2"=>["var3","var4","var5"] }
bar = { "key3"=>["var6"],"key4"=>["var7","var8","var9"],"key5"=>["var10","var11","var12"] }
baz = { "key6"=>["var13","var14","var15","var16"] }
merged = [foo, bar, baz].reverse.inject({}, :merge)
args.inject([]) do |array, key|
array += Array(merged[key])
end
end
In general, I wouldn't define a method with built-in data, but I'm going to leave it in to be closer to your original method. Hash#merge combines two hashes and overwrites any duplicate keys in the original hash with those in the argument hash. The Array() call coerces an array even when the key is not present, so you don't need to explicitly handle that error.
I would encourage you to look up the inject method - it's quite versatile and is useful in many situations. inject uses its own accumulator variable (optionally defined as an argument) which is yielded to the block as the first block parameter.

How can I get a reference to a method that contains the arguments used for invocations, in Ruby?

Given this code:
a = {1=>2}
m = a.method(:[])
I know that I can now use :
value = m.call(1)
and it will return 2. The thing is, what do I need to change so that I can call the method directly like :
m.call()
and it will get the 1 sent as a parameter? It would be nice to be able to write something like :
m = a.method(:[],1) # where the symbol is the method, and 1 will be the parameter it will be called with
The thing is, I'd like to delay the execution of certain parts of my script until some objects get created, and I'd like to avoid rewriting EVERYTHING to use lambdas.
Basically, what you want is a way to curry the function.
http://en.wikipedia.org/wiki/Curry_function
This can be done in many different ways, one of which:
def curry(method, *params)
lambda { send method, *params }
end
You can add this to Hash's metaclass, or to a module you want to include in some of your objects, etc. Then, calling it becomes the usecase you wanted:
irb(main):001:0> a = {1 => 2}
=> {1=>2}
... # add curry to Hash's metaclass
irb(main):011:0> m = a.curry :[], 1
=> #<Proc:0xb76e2154#(irb):8>
irb(main):012:0> m.call
=> 2
There's more than one way to do it, I'm sure.
a = {1=>2}
class << a
def fetch_what(key)
Proc.new { self[key] }
end
end
....
m = a.fetch_what(1)
m.call() # -> 2
It sounds like you should attach the method parameters to the object you're calling the method on, and have the method access them as instance variables.
In terms of simple refactoring steps:
Introduce new instance variables, one per method parameter.
Introduce new accessors for the instance variables.
Refactor the method to use the instance variables if the parameters are not supplied.
Refactor the calling code to set the instance variables through the accessors, at some point prior to the method call.
Refactor the calling code to pass no parameters in the method call.
As an example, refactor calling code like this:
widget = Widget.new
assembly_method = widget.method(:assemble)
# Time passes...
assembly_method.call(:electric, :smooth)
to work like this:
widget = Widget.new
widget.frombulator = :electric
widget.jazzifier = :smooth
assembly_method = widget.method(:assemble)
# Time passes...
assembly_method.call
It's not sexy or clever, but it will result in code that expresses its intent, and odds are good that it will address the real problem, namely that something is missing from your model.

Elegant way of duck-typing strings, symbols and arrays?

This is for an already existing public API that I cannot break, but I do wish to extend.
Currently the method takes a string or a symbol or anything else that makes sense when passed as the first parameter to send
I'd like to add the ability to send a list of strings, symbols, et cetera. I could just use is_a? Array, but there are other ways of sending lists, and that's not very ruby-ish.
I'll be calling map on the list, so the first inclination is to use respond_to? :map. But a string also responds to :map, so that won't work.
How about treating them all as Arrays? The behavior you want for Strings is the same as for an Array containing only that String:
def foo(obj, arg)
[*arg].each { |method| obj.send(method) }
end
The [*arg] trick works because the splat operator (*) turns a single element into itself or an Array into an inline list of its elements.
Later
This is basically just a syntactically sweetened version or Arnaud's answer, though there are subtle differences if you pass an Array containing other Arrays.
Later still
There's an additional difference having to do with foo's return value. If you call foo(bar, :baz), you might be surprised to get [baz] back. To solve this, you can add a Kestrel:
def foo(obj, arg)
returning(arg) do |args|
[*args].each { |method| obj.send(method) }
end
end
which will always return arg as passed. Or you could do returning(obj) so you could chain calls to foo. It's up to you what sort of return-value behavior you want.
A critical detail that was overlooked in all of the answers: strings do not respond to :map, so the simplest answer is in the original question: just use respond_to? :map.
Since Array and String are both Enumerables, there's not an elegant way to say "a thing that's an Enumberable, but not a String," at least not in the way being discussed.
What I would do is duck-type for Enumerable (responds_to? :[]) and then use a case statement, like so:
def foo(obj, arg)
if arg.respond_to?(:[])
case arg
when String then obj.send(arg)
else arg.each { |method_name| obj.send(method_name) }
end
end
end
or even cleaner:
def foo(obj, arg)
case arg
when String then obj.send(arg)
when Enumerable then arg.each { |method| obj.send(method) }
else nil
end
end
Perhaps the question wasn't clear enough, but a night's sleep showed me two clean ways to answer this question.
1: to_sym is available on String and Symbol and should be available on anything that quacks like a string.
if arg.respond_to? :to_sym
obj.send(arg, ...)
else
# do array stuff
end
2: send throws TypeError when passed an array.
begin
obj.send(arg, ...)
rescue TypeError
# do array stuff
end
I particularly like #2. I severely doubt any of the users of the old API are expecting TypeError to be raised by this method...
Let's say your function is named func
I would make an array from the parameters with
def func(param)
a = Array.new
a << param
a.flatten!
func_array(a)
end
You end up with implementing your function func_array for arrays only
with func("hello world") you'll get a.flatten! => [ "hello world" ]
with func(["hello", "world"] ) you'll get a.flatten! => [ "hello", "world" ]
Can you just switch behavior based on parameter.class.name? It's ugly, but if I understand correctly, you have a single method that you'll be passing multiple types to - you'll have to differentiate somehow.
Alternatively, just add a method that handles an array type parameter. It's slightly different behavior so an extra method might make sense.
Use Marshal to serialize your objects before sending these.
If you don't want to monkeypatch, just massage the list to an appropriate string before the send. If you don't mind monkeypatching or inheriting, but want to keep the same method signature:
class ToBePatched
alias_method :__old_takes_a_string, :takes_a_string
#since the old method wanted only a string, check for a string and call the old method
# otherwise do your business with the map on things that respond to a map.
def takes_a_string( string_or_mappable )
return __old_takes_a_string( string_or_mappable ) if String === string_or_mappable
raise ArgumentError unless string_or_mappable.responds_to?( :map )
# do whatever you wish to do
end
end
Between those 3 types I'd do this
is_array = var.respond_to?(:to_h)
is_string = var.respond_to?(:each_char)
is_symbol = var.respond_to?(:to_proc)
Should give a unique answer for [], :sym, 'str'

Resources