Does Hash.slice support strings? - ruby-on-rails

Does Hash.slice support strings ?
so for example
a = {"b" => 1, "c" => 2}
a.slice("b")
If not then how does it react to strings ?
I am trying to track down a bug where data is being lost and I think it is because the keys in hashes switch intermittently between strings and symbols. Filtering is done exclusively using Hash.slice(*keys)

In Rails, Hash#slice with a single key value (such as a string) from the hash returns a hash containing the matching key-value pair, or nil if it isn't matched to a key. Hash#slice takes any number of arguments, each representing a key. See the documentation here, and note *keys argument.

Related

Two way lookup of id vs name ruby

I have a hash in which id is the key and name is the value. Both id and value are unique.
Something like this:
h[1] = "ABC"
h[3] = "DEF"
So, if I am given the key of 1, I can easily return a value "ABC".
I need to do a reverse lookup as well, which means if I am given a value of "DEF", I should return 3.
Also, instead of a single value or single key to do the lookup,
I may be provided with an array of values or array of keys instead.
Should I implement two hashes, one for each, or is there any other way in ruby or rails to achieve that?
Edit: This question is not related to finding a key by its value in a hash. It is related to doing a two way lookup not in O(n) time with a better method other than creating two separate hashes.
You can use Hash#invert as below,
reversed_h = h.invert
reversed_h['DEF']
# => 3
You can get your key this way:
hash.key(value) => key
Hash#key
h = { 1 => 'ABC', 3 => 'DEF' }
puts h.key('DEF')
#=> 3

ruby on rails: can't understand code

I'm newbie in ruby on rails, trying to rewrite rails project in PHP, can someone explain me please what next lines of code do?
def payment_types_billing
I18n.t("customer.payment_types_billing").inject({}){|memo,(k,v)|
memo[k.to_s.to_i] = v; memo}.
merge({
PaymentType::ACCOUNTCREDIT => I18n.t("customer.payment_types_billing.#{PaymentType::ACCOUNTCREDIT}",
:amount => number_to_currency(get_account_credit))
})
end
I don't understand part after .inject, if someone can just explain that part in human language, I will be very thankful. :)
These methods work in conjunction. By calling payment_types, the following will occur:
First it grabs a section of the localization yaml (likely in config/locales/en.yml). For more on internationalization/localization, see this!
I18n.t("customer.payment_types_billing")
Then it runs an inject block on the resulting enumerable (in this case a hash), with the intent of returning a newly-formed result (see about .inject here)
.inject({}){|memo,(k,v)| memo[k.to_s.to_i] = v; memo}
The result of this block appears to be a hash whose keys were the keys of the retrieved hash, converted to integers (without knowing the data being accessed, I can't know how this is meant to function).
Addendum:
I suspect the purpose of the above block is to assign integer keys to a new hash (something that is impossible otherwise). Seeing the later steps with the invert, this would mean that the final printed hash will have integer values, not strings.
It then adds two new key value pairs to the hash:
.merge({PaymentType::ACCOUNTCREDIT => I18n.t("customer.payment_types_billing.#{PaymentType::ACCOUNTCREDIT}", :amount => number_to_currency(get_account_credit))})
The first pair has a key equal to ACCOUNTCREDIT, with another value retrieved from the YAML. The second is the key :amount, with the value of "get_account_credit" (presumably a method with a decimal output) converted to currency for the current region.
As we reach the actual content of the payment_types method, the results from above (the newly-formed hash) is a block with a delete condition. If get_account_credit is returning a non-positive number, the ACCOUNTCREDIT keyed pair is deleted
.delete_if {|key, value| (key == PaymentType::ACCOUNTCREDIT && get_account_credit <= 0) }
Finally, the hash is inverted (the keys become the values, and the values become the keys):
.invert

How default parameters are interpreted when default values are empty hashes

I was reading this SO question: https://stackoverflow.com/a/4402761/2379703 and the last post showing the rails render impl was interesting. It's signature is:
def render(options = {}, locals = {}, &block)
If the first argument is a plain string, it assigns that to options and the rest is interpreted as a hash and assigned to locals. For example:
render('partial_name', key: 'value', key2:, 'value2')
Which results in:
options = "partial_name"
locals = {key: 'value', key2:, 'value2'}
If you just pass key/value pairs, it assumes you passed a single argument of a hash and assigns it all to options and leaves locals empty:
render(partial: 'partial_name', key: 'value', key2", 'value2')
Which results in:
options = {partial: 'partial_name', key: 'value', key2:, 'value2'}
locals = {}
So my question really comes down to is: What is the logic that ruby uses to figure out which parameter gets assigned what when there are multiple optional parameters? Furthermore, it seems that hashes make the answer to this question more interesting since hashes clearly don't need to be delimited with outer {} when passed in as arguments.
A secondary observation, in a test where I used the same signature for a test method like render, and I passed the following in:
render(key: 'value', key2: 'value2', 'string')
And that results in a syntax error:
test_hash_param.rb:15: syntax error, unexpected ')', expecting =>
Why is this? Why doesn't it assign the two key/value pairs to options and sets locals to 'string'?
However this works as I assumed it would:
render({key: 'value', key2: 'value2'}, 'string')
Firstly, you can only pass hash without {} brackets as a last argument to the method. Otherwise it would be much harder for interpreter to find out where does one param ends and another starts.
Having that said, when ruby sees a list of hash-like arguments at the end of argument list, it will always treat it as a single hash. Hence in your example only options have assigned value, as locals has not been passed and default value has been used. There are a lot of issue you can find here here on stackoverflow being result of that. If you need to pass two separate hashes, you need to wrap at least first of them in brackets (and naturally the second one as well if it is not the last argument)
because you're passing hashes without boundaries, ruby has to make decisions about how to interpret them. instead of picking an arbitrary place to divide your key:value pairs into two hashes, it will just group them all in one hash. that means:
render( foo: "bar", hello: "world", "bananas")
gets read as one hash, because it can't tell where you want to end the hash. 'bananas' gets included as a key(because strings can be keys) and pops a syntax error because you didn't assign it a value.
options and locals don't have to be hashes though, because ruby variable types are dynamic. their default value is an empty hash, but if you pass two strings, they'll both be assigned strings. when you pass one string, that gets assigned to options because it is a complete variable. when you pass a symbol/string and a hash rocket though(or a symbol with the colon flipped), you're telling ruby "this is the start of a hash" so it starts looking for key:value pairs. in order to end that hash before the end of your arguments so that you can pass another argument, you have to explicitly tell ruby to stop looking for key:value pairs.

Elegantly extracting all strings from an arbitrarily deep hash

My goal is to determine whether there is a blank in a hash like this:
{"departure_time_slots"=>{"date"=>[""], "time"=>{"start"=>[""], "end"=>[""]}}}
The strings are nested arbitrary times. I do not want to check each of them manually. It would be nice if I can extract all the strings regardless of their depth. Is there a simple way to do that?
Here's an example of how you can extract the values. However you will still have the problem of matching the values to the keys. You can't really have both at the same time.
# Extend the Hash class
class Hash
def recursive_values
self.values.collect { |v|
v.is_a?(Hash) ? v.recursive_values : v
}.flatten
end
end
Usage:
h = {"departure_time_slots"=>{"date"=>[""], "time"=>{"start"=>[""], "end"=>[""]}}}
h.recursive_values
=> ["", "", ""]
It will be better if you will use sth like that:
departure_time_slots = {:date => Time.new, :time_start => nil, :time_end => nil}
And when you use keys in Hash it is good practise to using Symbols for keys. (http://www.ruby-doc.org/core-1.9.3/Symbol.html)
No not possible. Because they are totally present in different scope with respect to each other.
For e.g.. keys start and end is totally unknown and masked from the departure_time_slots object in the example above.
One round abut way could be, getting all the values of the hashmap which are of type hashmap again and obtaining their keys recusively.
Fetch keys of departure_time_slots and then from the value list of that map, find all the keys from every value, if that were to be a hashmap. Other than that, I don't think there is another way.
P.S. On side note, see if u can modify your structure to an array where elements can also be arrays, and try and use flatten concept of arrays. :P

Ordering a hash to xml: Rails

I'm building an xml document from a hash. The xml attributes need to be in order. How can this be accomplished?
hash.to_xml
Ruby 1.8's hash aren't in insertion order. With ruby 1.9, they will be.
However rails offers an alternative to that, the class OrderedHash.
my_hash = ActiveSupport::OrderedHash.new
my_hash[:key] = 'value'
my_hash[:second_key] = 'second value'
This hash is in fact an array of that format :
[[:key, 'value'], [:second_key, 'second value']]
The entries remains in the order you inserted them.
And you can access them like with any other hash.
h = Hash[:x,123,:a,553,:d,949,:e,5321]
=> {:e=>5321, :x=>123, :a=>553, :d=>949}
h.sort { |x,y| x[0].to_s <=> y[0].to_s }
=> [[:a, 553], [:d, 949], [:e, 5321], [:x, 123]]
The usual ways of sorting a hash is by key or value. Have a look here:
hash.sort
More complex sorts can be accomplised however by utilizing the spaceship operator
What order did you want them to be in? You shouldn't expect them to be in insertion order. From the docs for Hash:
The order in which you traverse a hash
by either key or value may seem
arbitrary, and will generally not be
in the insertion order.
If you need them to be in a specific order you can determine just from the keys/values (e.g. order the attribute names alphabetically), you'll need to apply that ordering explicitly.
This piece of code I've just made for i18n-js might help you out as it convert Hash to ActiveSupport::OrderedHash if needed then sort it's key by natural order.
http://seb.box.re/2010/1/15/deep-hash-ordering-with-ruby-1-8

Resources