Ruby simple map object to has not working - ruby-on-rails

I have a simple problem but I cannot find a solution.
I have a Forum model (active record) with several fields.
I'm creating a class method that return to me an has with one value as key (not the id) and the other as value.
This is my method:
Forum.all.map { |f| [f.old_id => f.icon.url(:micro) ]}
It returns
[[{10=>"/images/fallback/icon_fallback.png"}],
[{6=>"/images/fallback/icon_fallback.png"}],
[{18=>"/images/fallback/icon_fallback.png"}]]
instead of
{10=>"/images/fallback/icon_fallback.png", 6=>"/images/fallback/icon_fallback.png", 18=>"/images/fallback/icon_fallback.png"}
What is the error?

In your code, map returns an array and the square brackets produce arrays containing hashes.
res = {}
Forum.all{|f| res[f.old_id] = f.icon.url(:micro) }

in short you can just modify like this, change square brackets to curly brackets:
Forum.all.inject({}) { |r,f| r.merge!(f.old_id => f.icon.url(:micro)) }

You can make a minimal change to your code and receive your needed result by using to_h:
Forum.all.map { |f| [f.old_id, f.icon.url(:micro)] }.to_h

Yes, you can use reduce or inject method, or just construct the Hash from Arrays:
Hash[Forum.all.map { |f| [f.old_id, f.icon.url(:micro) ]}]
for ruby-2.0 you can use #to_h method:
Forum.all.map { |f| [f.old_id, f.icon.url(:micro) ]}.to_h

use active supports each_with_object:
Forum.all.each_with_object({}) { |f, h| h[f.old_id] = f.icon.url(:micro) }

Related

Re-ordering hash items to have some appear at the end

I'm returning a hash of form inputs, which at the moment looks like:
hash = {
"body"=>:text,
"button_text"=>:string,
"category"=>:integer,
"dashboard"=>:boolean,
"feature"=>:integer,
"featured_from"=>:datetime,
"featured_to"=>:datetime,
"global"=>:boolean,
"hyperlink"=>:string,
"jobs"=>:boolean,
"labs"=>:boolean,
"leaderboard"=>:boolean,
"management"=>:boolean,
"news"=>:boolean,
"objectives"=>:boolean,
"only_for_enterprise_users"=>:boolean,
"published_at"=>:datetime,
"title"=>:string,
"workflow_state"=>:string
}
I need to place the following keys at the end:
["dashboard", "jobs", "labs", "management", "news", "objectives", "global"]
Which will leave me with:
{
"body"=>:text,
"button_text"=>:string,
"category"=>:integer,
"feature"=>:integer,
"featured_from"=>:datetime,
"featured_to"=>:datetime,
"hyperlink"=>:string,
"only_for_enterprise_users"=>:boolean,
"published_at"=>:datetime,
"title"=>:string,
"workflow_state"=>:string,
"dashboard"=>:boolean,
"jobs"=>:boolean,
"labs"=>:boolean,
"leaderboard"=>:boolean,
"management"=>:boolean,
"news"=>:boolean,
"objectives"=>:boolean,
"global"=>:boolean
}
All the links I've found relate to transforming keys / values without re-ordering, and outside of manually deleting each key and then reinserting I can't see another way of getting my desired output.
Is there an easy way to achieve what I need?
Thanks in advance
You can try following,
(hash.keys - end_keys + end_keys).map { |key| [key, hash[key]] }.to_h
hash = { "body"=>:text, "button_text"=>:string, "category"=>:integer,
"dashboard"=>:boolean, "feature"=>:integer }
enders = ["button_text", "dashboard"]
hash.dup.tap { |h| enders.each { |k| h.update(k=>h.delete(k)) } }
See Object#tap, Hash#update (aka merge!) and Hash#delete.
dup may of course be removed if hash can be mutated, which may be reasonable as only the keys are being reordered.

How to refactor each function with map in Ruby?

I have a loop building a hash for use in a select field. The intention is to end up with a hash:
{ object.id => "object name", object.id => "object name" }
Using:
#hash = {}
loop_over.each do |ac|
#hash[ac.name] = ac.id
end
I think that the map method is meant for this type of situation but just need some help understanding it and how it works. Is map the right method to refactor this each loop?
Data transformations like this are better suited to each_with_object:
#hash = loop_over.each_with_object({}) { |ac, h| h[ac.name] = ac.id }
If your brain is telling you to use map but you don't want an array as the result, then you usually want to use each_with_object. If you want to feed the block's return value back into itself, then you want inject but in cases like this, inject requires a funny looking and artificial ;h in the block:
#hash = loop_over.inject({}) { |h, ac| h[ac.name] = ac.id; h }
# -------------------- yuck -----------------------------^^^
The presence of the artificial return value is the signal that you want to use each_with_object instead.
Try:
Hash[loop_over.map { |ac| [ac[:name], ac[:id]] }]
Or if you are running on Ruby 2:
loop_over.map { |ac| [ac[:name], ac[:id]] }.to_h
#hash = Hash[loop_over.map { |ac| {ac.name => ac.id} }.map(&:flatten)]
Edit, a simpler solution as per suggestion in a comment.
#hash = Hash[ loop_over.map { |ac| [ac.name, ac.id] } ]
You can simply do this by injecting a blank new Hash and performing your operation:
loop_over.inject({}){ |h, ac| h[ac.name] = ac.id; h }
Ruby FTW
No a map isn't the correct tool for this.
The general use-case of a map is to take in an array, perform an operation on each element, and spit out a (possibly) new array (not a hashmap) of the same length, with the individual element modifications.
Here's an example of a map
x = [1, 2, 3, 4].map do |i|
i+1 #transform each element by adding 1
end
p x # will print out [2, 3, 4, 5]
Your code:
#hash = {}
loop_over.each do |ac|
#hash[ac.name] = ac.id
end
There is nothing wrong with this example. You are iterating over a list, and populating a hashmap exactly as you wished.
Ruby 2.1.0 introduces brand new method to generate hashes:
h = { a: 1, b: 2, c: 3 }
h.map { |k, v| [k, v+1] }.to_h # => {:a=>2, :b=>3, :c=>4}
I would go for the inject version, but use update in the block to avoid the easy to miss (and therefore error prone) ;h suffix:
#hash = loop_over.inject({}) { |h, ac| h.update(ac.name: ac.id) }

Iterate through array to construct hash in Ruby

Currently I have:
cache_hash = {}
array = [:id, :content, :title]
data_source = some_active_record_object
array.each{ |k| cache_hash[k] = data_source.send(k) }
cache_hash
#=>{:id=>"value1", :content=>"value2", :title=>"value3"}
I'm wondering if there is a better way to iterate through the array and get the hash out.
Write as below :
cache_hash = Hash[array.map { |k| [k, data_source.send(k)] }]
Or use new #to_h
cache_hash = array.map { |k| [k, data_source.send(k)] }.to_h
I'm not sure if you want to follow this route, but here is an alternative with AR query:
Foo.select('id, content, title').to_a.map(&:serializable_hash)
Foo is the model you're operating on.
This is similar to #Arup's answer using map. The cool thing about map functions, in any programming language (not just Ruby), is that you can also express them in terms of
an inject function (also called fold, reduce, or aggregate in other languages):
cache_hash = array.inject({}) do |hash, key|
hash[key] = data_source.send key
hash
end
Not as clear as Arup's answer using map, but kind of cool to know anyways.

Converting http_params to hash

I can obtain an array from the string
http_params="created_end_date=2013-02-28&created_start_date=2013-01-01&page_size=50&offset=0&order_id=0D1108211501118%0D%0A0D11108211501118%0D%0Ac%0D%0AD%0D%0ADK212071409743%0D%0AKK30109110100%0D%0AKK30111140300%0D%0AKK30111140400%0D%0AKK30115120100%0D%0AKK30115150100&page_number=1"
So I did myarray=http_params.split("&"):
myarray=["created_end_date=2013-02-28", "created_start_date=2013-01-01", "page_size=50", "offset=0", "order_id=0D1108211501118%0D%0A0D11108211501118%0D%0Ac%0D%0AD%0D%0ADK212071409743%0D%0AKK30109110100%0D%0AKK30111140300%0D%0AKK30111140400%0D%0AKK30115120100%0D%0AKK30115150100", "page_number=1"]
I need to convert this to a hash myhash, so that I can make a Rest Client post call for myhash.to_json. Basically it should be key,value pairs like:
{:created_end_date=>"2013-02-28",:created_start_date=>"2013-01-01"....}
I know that the inverse operation can be done like this:
http_params = myhash.map{|k,v| "#{k}=#{v}"}.join('&')
but I am unable to come up with neat code for this.
What's the best way I should go about this?
require 'cgi'
hash = CGI::parse http_params
Or you can use:
hash = Rack::Utils.parse_nested_query http_params
Which does not return the values as arrays.
With pure Ruby methods, you can convert your string into a Hash as follows:
"a=1&b=2".split('&').map { |h| Hash[*h.split("=")] }
=> [{"a"=>"1"}, {"b"=>"2"}]
A blog post how to operate on Ruby collections is here: http://thinkingonthinking.com/map-reduce-in-ruby/
To get symbols as keys, a small additional step is necessary:
"a=1&b=2".split('&').map { |h| hs = h.split("="); Hash[hs[0].to_sym, hs[1]] }
=> [{:a=>"1"}, {:b=>"2"}]
As last step, a merge of the inner Hash elements has to be done. This can be done like:
"a=1&b=2".split('&').map { |h| hs = h.split("="); Hash[hs[0].to_sym, hs[1]] }.inject({}) { |s, h| s.merge(h) }
=> {:a=>"1", :b=>"2"}

Ruby on Rails: Array to Hash with (key, array of values)

Lets say I have an Array of content_categories (content_categories = user.content_categories)
I now want to add every element belonging to a certain categorie to content_categories with the category as a key and the the content-item IDs as elements of a set
In PHP something like this is possible:
foreach ($content_categories as $key => $category) {
$contentsByCategoryIDArray = Category.getContents($category[id])
$content_categories[$key][$contentsByCategoryIDArray]
}
Is there an easy way in rails to do this?
Greets,
Nico
Your question isn't really a Rails question, it's a general Ruby programming question.
Your description isn't very clear, but from what I understand, you want to group IDs for common categories using a Hash. There are various other ways of doing this, but this is easy to understand::
ary = [
'cat1', {:id => 1},
'cat2', {:id => 2},
'cat1', {:id => 3}
]
hsh = {}
ary.each_slice(2) { |a|
key,category = a
hsh[key] ? hsh[key] << category[:id] : hsh[key] = [category[:id]]
}
hsh # => {"cat1"=>[1, 3], "cat2"=>[2]}
I'm using a simple Array with a category, followed by a simple hash representing some object instance, because it makes it easy to visualize. If you have a more complex object, replace the hash entries with those objects, and tweak how you access the ID in the ternary (?:) line.
Using Enumerable.inject():
hsh = ary.each_slice(2).inject({}) { |h,a|
key,category = a
h[key] ? h[key] << category[:id] : h[key] = [category[:id]]
h
}
hsh # => {"cat1"=>[1, 3], "cat2"=>[2]}
Enumerable.group_by() could probably shrink it even more, but my brain is fading.
I'd use Enumerable#inject
content_categories = content_categories_array.inject({}){ |memo, category| memo[category] = Category.get_contents(category); memo }
Hash[content_categories.map{|cat|
[cat, Category.get_contents(cat)]
}]
Not really the right answer, because you want IDs in your array, but I post it anyway, because it's nice and short, and you might actually get away with it:
content_categories.group_by(&:category)
content_categories.each do |k,v|
content_categories[k] = Category.getContents(v)
end
I suppose it's works
If i understand correctly, content_categories is an array of categories, which needs to be turned into a hash of categories, and their elements.
content_categories_array = content_categories
content_categories_hash = {}
content_categories_array.each do |category|
content_categories_hash[category] = Category.get_contents(category)
end
content_categories = content_categories_hash
That is the long version, which you can also write like
content_categories = {}.tap do |hash|
content_categories.each { |category| hash[category] = Category.get_contents(category) }
end
For this solution, content_categories must be a hash, not an array as you describe. Otherwise not sure where you're getting the key.
contents_by_categories = Hash[*content_categories.map{|k, v| [k, Category.getContents(v.id)]}]

Resources