How to create a grouped select from a flat hash of values? - ruby-on-rails

I am pulling a flat hash of parameters from an external API into my Rails app. I need to present these parameters to the user in a select field.
The hash is formatted as follows:
[["Name", "ID", "Category"]]
I'd like to present this as a grouped select. But I believe the grouped_select tag takes a hierarchical hash, not a flat hash?
Is there an easy way to restructure this hash into a structured hash? i.e.,
['CategoryA', [['Name-A1','ID-A1'],['Name-A2', 'ID-A2']]
For reference, I'm currently handling the hash in my controller like this
#hash = session["hash"].map { |h| [ h["name"], h["id"], h["category"] ] }
and in the view
<%= f.select :hash_id, #hash %>
This gives me an unordered list of options. How can I group on h["category"]?
Thanks for any pointers.
EDIT
By the way, I;ve already tried group_by as it seemed the obvious way to go. But I'm getting can't convert String into Integer. Perhaps I'm not using it correctly?
hash.group_by {|h| h["category"] }.map { |h| [ h["name"], h["id"], h["category"] ] }

hash.group_by {|h| h["category"] }.map { |h| [ h[0], h[1].map{|e| [e["name"],e["id"]]} ] }

Related

Ruby or Ruby on Rails group_by on dynamically changing parameters

I want to change group_by parameters based on user input.
Example:
Data on which i am going to group:
result = [{"result_1"=>"01","result_2"=>"August","result_3"=>"2016","result_4"=>264},{"result_1"=>"02","result_2"=>"August","result_3"=>"2016","result_4"=>49},{"result_1"=>"03","result_2"=>"August","result_3"=>"2016","result_4"=>53}]
I know how a ruby group_by works with multiple parameters.
To group_by on result_1 and result_2.It can be done this way:
result.group_by{|h| [h["result_1"],h["result_2"]}
Above code groups the data based on result_1 and result_2.Here the parameters are hard_coded.
I want group_by based on parameters passed dynamically.As
dynamic_parameter = ["result_3","result_4"]
I want group_by to be as
result.group_by{|h| [h["result_3"],h["result_4"]]}
i want to do it as roughly:
dynamic_parameters.each do |parameter|
result.group_by{|h| [h[parameters]}
end
or similar to:
result.group_by{|h| [h[parameters[0]],h[parameters[1]]}
These result_3 and result_4 parameters are based on dynamic_parameter array values.
Note: Group_by should vary based on the array elements.
If I understood your question correctly then something like this would do the job:
result.group_by do |h|
dynamic_parameters.map{ |p| h[p] }
end
Or one-liner:
result.group_by { |h| h.values_at(*dynamic_parameters) }

How to remove nested keys from a hash list in Rails

I am now trying for some hours to remove a nested hash key of a hash list.
I saw many solution non-nested hashs wich looks like this:
sample_hash = {"key1" => "value1", "key2" => "value2"}
sample_hash.except("key1")
This results in:
{"key2"=>"value2"}
But if I try to use the except method on a hash with nested key then it doesn't work.
Here my code:
nested_hash = {"key1"=>"value1", "key2"=>{
"nested_key1"=>"nestedvalue1",
"nested_key2"=>"nestedvalue2"
}
}
nested_hash.except("nested_key2")
The except() method returns the nested_hash without any changes. I have looked for a solution how I can pass nested hash-keys to the except method, but couldn't find anything. Is it even possible to pass nested keys to this method or should I use some other method which deletes a nested hash key from my hash list?
what about
Hash[nested_hash.map {|k,v| [k,(v.respond_to?(:except)?v.except("nested_key2"):v)] }]
=> {"key1"=>"value1", "key2"=>{"nested_key1"=>"nestedvalue1"}}
ugh.
The accepted solution is valid for the scenario given but if you're looking for something that will do this for arbitrarily nested hash tables then you're going to need a recursive solution. I couldn't find a suitable solution anywhere, so I wrote one here.
Reproduced here with annotations:
class Hash
def except_nested(key)
r = Marshal.load(Marshal.dump(self)) # deep copy the hashtable
r.except_nested!(key)
end
def except_nested!(key)
self.except!(key)
self.each do |_, v| # essentially dfs traversal calling except!
v.except_nested!(key) if v.is_a?(Hash)
end
end
end
adding it to the Hash class so that you can call it the same way you call except/except! anywhere else.
t = { a: '1', b: { c: '3', d: '4' } }
r = t.except_nested(:c)
# r => {:a=>"1", :b=>{:d=>"4"}}
# t => {:a=>"1", :b=>{:c=>"3", :d=>"4"}}
t.except_nested!(:c)
# t => {:a=>"1", :b=>{:d=>"4"}}
try
my_hash = Hash[nested_hash.map {|k,v| {k=>v.is_a? Array ? v.except("nested_key2") : v}}.map {|key, value| [key, value]}]
But this seems wrong, I wish I never started down this path, I'm willing to bet there is an easier way!
If you know that the nested key will always be there then you can just do
nested_hash['key2'].except!('nested_key2')
the whole nested_hash will now be lacking 'nested_key2'

Set first group of group_by?

Good day, I was wondering, is it possible if i make a selection with group_by like so
#p = Performance.includes(place: [:category]).
order("places.title ASC").
group_by{|p| p.place.category}
so if i want a specific category to be the first, what do i do?
EDIT 1
in view a parse through the results by #p.each do |p|
The return value of group_by is just a normal hash, so you can apply sort_by on it to place your desired category first:
group_by { |p| p.place.category }.sort_by { |k,v| (k=="category name") ? "" : k }
where category name is the name of the category you want to prioritize (the empty string make it come first in the sort results, everything else will just be sorted alphabetically).
This will transform the hash into an array. If you want to keep the data in hash form, wrap the result in Hash[...]:
Hash[group_by { |p| p.place.category }.sort_by { |k,v| (k=="category name") ? "" : k }]
See also this article on sorting hashes: http://www.rubyinside.com/how-to/ruby-sort-hash
UPDATE:
A slightly less processor-intensive alternative to sorting:
grouped = group_by { |p| p.place.category }
Hash[*grouped.assoc("category name")].merge(grouped.except("category name"))
There might be a simpler way to do this, but basically this prepends the key and value for "category name" to the head of the hash.
Although I think shioyama's answer might help you, I doubt you really need the sort process. As shio correctly states, the return value of your sort_by is a hash. So why dont you just access the value, which you want as the first value, simply by using it as hash-key?

how to work with array of hashes

[[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]]
is the array
how can I get the value corresponding to particular value.
say High returns 5 in this.
or how to convert this array of hashes to an array so that searching becomes easy.
I tried:
find_all { |v| v['name'] == "Low" }
but it says:
cant convert String to Integer
please provide some guidance
How about making a single hash out of it for efficient querying?
arr.flatten.reduce(:merge)
#=> {"Postponed"=>10, "Low"=>3, "Medium"=>4, "High"=>5}
If you have some code like:
array = [[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]]
Then turn it into an ruby hash:
hash = array.inject({}) {|h, e| h.merge(e.first) }
# => {"Postponed"=>10, "Low"=>3, "Medium"=>4, "High"=>5}
So you can find 'Low' value easily :
hash['Low']
# => 3
EDIT: The answer of Mark Thomas is pretty great, and shorter than the inject since it does the same thing. He wrote it before I answered. Nice ;)
In the general case, the hashes won't be unique, so you need to filter rather than pick one via indexing. For example, let's say you have this:
arr = [[{:apple => 'abc'}], [{:banana => 'def'}], [{:coconut => 'ghi'}]]
# => [[{:apple=>"abc"}], [{:banana=>"def"}], [{:coconut=>"ghi"}]]
Now let's suppose you want to get the value corresponding to any hash with a :coconut key. Then just use:
arr.flatten.map { |h| h[:coconut] }.compact
# => ["ghi"]
That gives you the list of answers. In this case there's only one matching key, so there's only one entry in the array. If there were other hashes that had a :coconut key in there, then you'd have something like:
# => ["ghi", "jkl", "mno"]
On the whole, though, that's a very unusual data structure to have. If you control the structure, then you should consider using objects that can return you sensible answers in the manner that you'd like, not hashes.
You could simply call #flatten on the original array. That would give you an array of hashes. What I think you would really want is just one hash.
1.8.7 :006 > [[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]].flatten
=> [{"Postponed"=>10}, {"Low"=>3}, {"Medium"=>4}, {"High"=>5}]
I would ask, what are you doing to get that original structure? Can that be changed?
How about this?
arr = [
[{"Postponed"=>10}],
[{"Low"=>3}],
[{"Medium"=>4}],
[{"High"=>5}]
]
arr1 = []
arr.each{|a|
arr1.push(a[0])
}
Although I wonder if you really just want to get one hash, which you'd do like so:
myHash = {}
arr.each{|a|
a[0].each{|b, c|
myHash[b] = c
}
}
You would then access it like myHash["Postponed"]

My hashes are stacking unordered..What's a loop that can select them by their Hash ID?

My Hashes are appearing like this:
{"6"=>{":amount_paid"=>"100.00", ":date_paid"=>"4/22/2009"},
"0"=>{":amount_paid"=>"100.00", ":date_paid"=>"2/27/2008"},
"1"=>{":amount_paid"=>"80.00", ":date_paid"=>"3/27/2008"},
"2"=>{":amount_paid"=>"100.00", ":date_paid"=>"5/8/2008"},
"3"=>{":amount_paid"=>"100.00", ":date_paid"=>"6/20/2008"},
"4"=>{":amount_paid"=>"100.00", ":date_paid"=>"9/22/2008"},
"5"=>{":amount_paid"=>"100.00", ":date_paid"=>"2/20/2009"}}
The order matters to me when I loop through it with this:
params[:payments].each_with_index do |item, idx|
In this way I can add the dates by which ever date came before them.
Is there a loop that could find the sequence of "0".."6" and remain close to the same syntax?
The only other alternative I can think of is to ensure that those params get stacked in order. They come from a form like this :
= text_field_tag "payments[0][:date_paid]"
= text_field_tag "payments[0][:amount_paid]"
= text_field_tag "payments[1][:date_paid]"
= text_field_tag "payments[1][:amount_paid]"
= submit_tag 'punch it chewy!'
Hashes are unordered in Ruby 1.8, and ordered by insertion in Ruby 1.9. You can sort your hash by the key by using Enumerable#sort as seen in this thread. What you get out isn't a Hash but an array of arrays, with the first element as the keys and the second as the values. You will need to unpack these to get what you want similar to the each_with_index.
params[:payments].sort { |a, b| a[0].to_i <=> b[0].to_i }.each do |x|
item = x[1]
index = x[0]
.....
end
This has a similar syntax:
(0..6).each do |idx| item=params[:payments][idx]
# ...
end
Hash apparently keeps keys in the order they are inserted ( http://www.ruby-doc.org/core/classes/Hash.html ), so you can re-create a sorted hash this way:
Hash[params[:payments].sort]
(Apparently since Ruby 1.9.2; maybe not in all implementations)
Hashes are unordered. There is a gem called facets which has a dictionary object that is ordered.
You could also convert the hash to an array and then sort the array.
thing = {"1" => {:paid => 100, :date => '1/1/2011'}, "2" => {:paid => 100, :date => '1/12/2011'}}
thing.to_a.sort
thing.inspect
returns: [["1", {:date=>"1/1/1900", :paid=>100}], ["2", {:date=>"1/1/1900", :paid=>100}]]
You can then loop through the array in the correct order.
sorted_payments = params[:payments].keys.sort.map {|k| params[:payments][k]}
returns an array of hashes ordered by the value of the keys, which you can then enumerate with .each. This is more generalized than doing (0..6), which might (or might not) be useful.
(0..6).each do |idx|
item = params[:payments][idx]
end

Resources