Explaining hashes in Ruby - ruby-on-rails

I'm reading a book Crafting Rails Applications by Jose Valim and have come across something that I don't understand. I'm wondering if someone can explain the difference in plain English between the three types of hash below.
For example, in what way is the nested hash (as its represented in this example) a nested hash. In other contexts, I understand nested hashes, but don't get it here.
In what way is an "array" a "key" in the second example. To me it looks just like an array with four variables.
In what way is the third example a hash with "hash as key".
Nested hash
#cached[key][prefix][name][partial]
Simple hash with array as key
#cached[[key, prefix, name, partial]]
Simple hash with hash as key
#cached[:key => key, :prefix => prefix, :name => name, :partial => partial]

The nested hash, is well, a nested hash. The example given, #cached[key][prefix][name][partial], is showing you the "path" to a particular value, so in this case the hash might look something like this:
#cache = {
key => {
prefix => {
name => {
partial => "value"
}
}
}
}
For the simple hash with an array as a key, they're using that 4-element array as one of the keys in the hash.
#cache = {
[key, prefix, name, partial] => "value",
another_key => "another value"
}
For the simple hash with hash as a key, they're using that hash (note that the {}'s for the hash are optional, which may cause some confusion) as one of the keys in the hash.
#cache = {
{:key => key, :prefix => prefix, :name => name, :partial => partial} => "value",
another_key => "another value"
}
Hope that helps!

A hash simply associates key objects to value objects. The keys and values can be anything.
If a value object is itself a hash, you could call it a "nested hash" because in some sense it is inside the main hash.
If a key object is an array, then you get a "hash with array as key".
If a key object is itself a hash, then you get a "hash with hash as key".
See amfeng's answer for a good visual representation of these different cases.
You will need to be somewhat familiar with Ruby syntax to identify the different cases when you see them.
For example, to understand #cached[[key, prefix, name, partial]] you need to know that [key, prefix, name, partial] represents an array, so what you have is like #cached[array], which means an array is being used as a key.
When you see something like #cached[key][prefix] you should know that it is equivalent to (#cached[key])[prefix] so the value object (#cached[key]) is some sort of object that responds to the [] method. In this case, it is a nested hash because the author told you so, but if you didn't know that context then it is possible for it to be something else.
When you see something like #cached[:key => key, :prefix => prefix, :name => name, :partial => partial] you should know it equivalent to #cached[{:key => key, :prefix => prefix, :name => name, :partial => partial}], which means we are using as hash as a key.

Related

Stuck with send hash with remote true

I was stuck at a point badly..when I was doing with Rails form_for submit request with remote:true with a hidden field containing array of hashes as below:
<%= f.hidden_field :staff_stat_data, :value =>[{a: "a"} , {b: "b"}] %>
then I am getting hash as a string in parameter like:
"{:a=>\"a\"} {:b=>\"b\"}"
Badly stuck with this.
You're not getting a hash, you're getting a string that kind of looks like a hash.
Remember that each parameter is just a string, that's how data is passed between clients and servers. Rails can sometimes receive an array, but only when the parameter names describe an array (e.g, "user_favourites[]").
If you want to pass a single string that represents an array or hash, you can use JSON to encode/parse the data.
In your view, first change the array to its JSON representation like this:
<%= f.hidden_field :staff_stat_data, :value => [{a: "a"} , {b: "b"}].to_json %>
Then in your controller, change it to a hash by parsing the JSON like this:
staff_stat_data = JSON.parse(params[:staff_stat_data])
This will return you an array, where each element is a hash, just like you want.
You can try this out easily in your Rails console.
json = [{a: "a"} , {b: "b"}].to_json # => "[{\"a\":\"a\"},{\"b\":\"b\"}]"
JSON.parse(json) # => [{a: "a"} , {b: "b"}]

Rails Hidden field tag - Remove "value" key from parameter

I have this code in my view:
<%= hidden_field_tag :comment_id, '1'%>
It essentially creates this for params
params = {"commit"=>"No Phrase to Add", "comment_id"=>"{:value=>1}"}
I want to extract the comment_id of 1 from the above hash. params[:comment_id][:value] throws an error, because I'm not looking at the key directly but a hash as a string instead.
How can I remove the value key or access the comment_id of 1 above?
goal : "comment_id" => 1
You could use:
eval(params["comment_id"])[:value]
=> 1
The eval would convert the string value of params["comment_id"] into an actual hash, and then you can easily retrieve the value from it.

Rails: How to artificially specify param key/value values through the URL?

I have to trigger an action of a particular website. However, it is supposed to handle params with certain key/value pairs (params = {:cost => 5, :state => "NY"}). Would there be a way to specify these params values in the URL? How would I provide these key/values otherwise?
show_things_path(:cost => 5, :state => "NY")
will add those key/values to params.
Edit:
for a string URL
"/show_things?cost=5&state='NY'"

How to parse key name with name(value) pairs into hash of key/value pairs with original value?

I have a large hash like this:
{"id"=>"1",
"contact_id"=>"15062422",
"status"=>"Complete",
"[question(12), option(24), piped_page(32]" => "Yes",
"[question(13), option(32)]" => "Robert",
"[question(14)]" => "Thing"}
I need to parse the keys that start with '[' to separate the name(value) pairs. The number of names (i.e. question, option, etc) in each key is variable but there are a known number of possibilities.
I'd like to convert each pair into a new has like this:
{:question => 12, :option => 24, :piped_page => 32, :value => "Yes"}
I've thought of using .to_s on each hash element and then doing a variety of string substitutions followed by eval, but the .to_s escapes the double quotes which really complicates things.
Any ideas?
You can use regex to solve it:
str = "[question(12), option(24), piped_page(32)]"
Hash[str.scan /(\w+)\((\w+)\)/]
=> {"question"=>"12", "option"=>"24", "piped_page"=>"32"}

Ruby on Rails: Interpreting a form input as an integer

I've got a form that allows the user to put together a hash.
The hashes desired end format would be something like this:
{1 => "a", 2 => "x", 3 => "m"}
I can build up something similar by having lots of inputs that have internal brackets in their names:
<%= hidden_field_tag "article[1]", :value => a %>
However, the end result is that builds a hash where all the keys are strings and not integers:
{"1" => "a", "2" => "x", "3" => "m"}
I'm currently fixing this by generating a new hash in the controller by looping over the input hash, and then assigning that to params. Is there a cleaner, DRYer way to do this?
Your params will always come in with string keys and values. The easiest way to fix this is to either write your own extension to Hash or simply inject as required:
numeric_keys = params['article'].inject({ }) do |h, (k, v)|
h[k.to_i] = v
h
end
Then you have a hash with the keys converted to integer values, as you like.
A simple extension might be:
class Hash
def remap_keys
inject({ }) do |h, (k, v)|
h[yield(k)] = v
h
end
end
end
This is much more generic and can be used along the lines of:
params['article'].remap_keys(&:to_i)
That depends a bit what you want to use it for. Maybe it is easier to just use strings as keys, and do the "conversion" when accessing the array (or not at all)?
It is also possible to build an array using something like
<%= hidden_field_tag "article[]", :value => "x" %>
this will return "article" as an array, and you can access it directly by index. However, there is no way to influence the position - the array will contain all values in order of appearance.
Lastly, you can make your own version of Hash or just modify the keys, as has been explained.

Resources