Isolating and displaying a specific element within a hash - ruby

I am currently having trouble writing a test that addresses the eligibility_settings of a record I have. I am having trouble pulling out one of the specific elements from this hash.
Specifically I want to test that by making a change elsewhere in a different function that changes the min age of a specific player, and so what I am really trying to test is the eligibility_settings.min_age. But i'm having trouble within my test isolating that out.
My hash looks like this
{
:name => "player1",
:label => "redTeam_1_1",
:group => "adult",
:teamId => 7,
:eligibility_settings => {
"min_age" => 18,
"player_gender" => "female",
"union_players_only" => true
}
}
However when I try looping through this hash, I am having trouble isolating that one element.
i've tried something like
team.get_players.first.map do |settings, value|
value.tap do |x, y|
y[3]
end
end
However It seems like what i've been trying, and my approach has not been working quite right.
Would anyone have any idea what I could do with this?

Although #SergioTulentsev gave the proper response, in the future if you are going to be looping through hashes, below is one way to iterate through the keys and grab the value you want.
hash = {
:name => "player1",
:label => "redTeam_1_1",
:group => "adult",
:teamId => 7,
:eligibility_settings => {
"min_age" => 18,
"player_gender" => "female",
"union_players_only" => true
}
}
hash.map do |settings, value|
p hash[:eligibility_settings]['min_age'] if settings == :eligibility_settings
end # output 18

Related

I can't find a way to create a simple multidimensional array or hash in Ruby

You Ruby pros will laugh but I'm having such a hard time with this. I've searched and searched and tried a lot of different things but nothing seems right. I guess I'm just used to dealing with arrays in js and php. Here is what I want to do; consider this pseudo code:
i = 0
foreach (items as item) {
myarray[i]['title'] = item['title']
myarray[i]['desc'] = item['desc']
i++
}
Right, so then I can loop through myarray or access 'title' and 'desc' by the index (i). Simplest thing in the world. I've found a few ways to make it work in Ruby but they've all been really messy or confusing. I want to know the right way to do it, and the cleanest.
Unless you are actually updating my_array (which implies that there is probably a better way to do this), you probably want map instead:
items = [
{'title' => 't1', 'desc' => 'd1', 'other' => 'o1'},
{'title' => 't2', 'desc' => 'd2', 'other' => 'o2'},
{'title' => 't3', 'desc' => 'd3', 'other' => 'o3'},
]
my_array = items.map do |item|
{'title' => item['title'], 'desc' => item['desc'] }
end
items # => [{"title"=>"t1", "desc"=>"d1", "other"=>"o1"}, {"title"=>"t2", "desc"=>"d2", "other"=>"o2"}, {"title"=>"t3", "desc"=>"d3", "other"=>"o3"}]
my_array # => [{"title"=>"t1", "desc"=>"d1"}, {"title"=>"t2", "desc"=>"d2"}, {"title"=>"t3", "desc"=>"d3"}]
I'm not quite sure why you are trying to do this, as it seems like items is already an array with hashes inside it, and in my code below, myarray is exactly the same as items.
Try using each_with_index instead of a foreach loop:
items.each_with_index do |item, index|
myarray[index] = item
end
If you have extra attributes in each item, such as a id or something, then you would want to remove those extra attributes before you add the item to myarray.
titles = ["t1", "t2", "t3"]
descs = ["d1", "d2", "d3"]
h= Hash.new
titles.each.with_index{ |v,i| h[i] = {title: "#{v}" } }
puts h[0][:title] #=> t1
puts h #=> {0=>{:title=>"t1"}, 1=>{:title=>"t2"}...}
descs.each.with_index{ |v,i| h[i] = h[i].merge( {desc: "#{v}" } ) }
puts h[0][:desc] #=> d1
puts h #=> {0=>{:title=>"t1", :desc=>"d1"}, 1=>...

Is there a built-in Ruby method for "reshaping" a hash?

I have this hash which I retrieve from a database:
original_hash = {
:name => "Luka",
:school => {
:id => "123",
:name => "Ieperman"
},
:testScores => [0.8, 0.5, 0.4, 0.9]
}
I'm writing an API and want to return a slightly different hash to the client:
result = {
:name => "Luka",
:schoolName => "Ieperman",
:averageScore => 0.65
}
This doesn't work because the method reshape doesn't exist. Does it exist by another name though?
result = original_hash.reshape do |hash|
{
:name => hash[:name],
:school => hash[:school][:name],
:averageScore => hash[:testScores].reduce(:+).to_f / hash[:testScores].count
}
end
I'm new to Ruby so thought I'd ask before I go off overriding core classes. I'm sure it must exist as I always find myself reshaping hashes when writing an API. Or am I totally missing something?
The implementation is dead simple but, like I said, I don't want to override Hash if I don't need to:
class Hash
def reshape
yield(self)
end
end
BTW, I know about this:
result = {
:name => original_hash[:name],
:school => original_hash[:school][:name],
:averageScore => original_hash[:testScores].reduce(:+).to_f / original_hash[:testScores].count
}
But sometimes I don't have an original_hash variable and instead I'm operating straight off a return value, or I'm inside a one liner where this block based approach would be convenient.
Real World example:
#get the relevant user settings from the database, and reshape the hash into the form we want
settings = users.find_one({:_id => oid(a[:userID])}, {:emailNotifications => 1, :newsletter => 1, :defaultSocialNetwork => 1}).reshape do |hash|
{
:emailNotifications => hash[:emailNotifications] == 1,
:newsletter => hash[:newsletter] == 1,
:defaultSocialNetwork => hash[:defaultSocialNetwork]
}
end rescue fail
If you're using Ruby >= 1.9, try a combination of Object#tap and Hash#replace
def foo(); { foo: "bar" }; end
foo().tap { |h| h.replace({original_foo: h[:foo]}) }
# => { :original_foo => "bar" }
Since Hash#replace works in-place, you might find this a bit safer:
foo().clone.tap { |h| h.replace({original_foo: h[:foo]}) }
But this is getting a bit noisy. I'd probably go ahead and monkey-patch Hash at this stage.
From an API perspective, you may be looking for a representer object to sit between your internal model, and the API representation (prior to format-based serialisation). This doesn't work using the shortest, convenient Ruby syntax inline for a hash, but is a nice declarative approach.
For instance, the Grape gem (other API frameworks are available!) might solve the same real-world problem as:
# In a route
get :user_settings do
settings = users.find_one({:_id => oid(a[:userID])}, {:emailNotifications => 1, :newsletter => 1, :defaultSocialNetwork => 1})
present settings, :with => SettingsEntity
end
# Wherever you define your entities:
class SettingsEntity < Grape::Entity
expose( :emailNotifications ) { |hash,options| hash[:emailNotifications] == 1 }
expose( :newsletter ) { |hash,options| hash[:newsletter] == 1 }
expose( :defaultSocialNetwork ) { |hash,options| hash[:defaultSocialNetwork] }
end
This syntax is more geared towards handling ActiveRecord, or similar models, and not hashes though. So not a direct answer to your question, but I think implied by you building up an API. If you put in a representer layer of some kind now (not necessarily grape-entity), you will be thankful for it later, as you'll be better able to manage your model-to-API data mappings when they need to change.
You can replace the call to "reshape" with the builtin method Object#instance_eval and it will work exactly as such. Note however that there may be some unexpected behavior since you evaluating code in the context of the receiving object (e.g. if using "self").
result = original_hash.instance_eval do |hash|
# ...
This abstraction does not exist in the core but people uses it (with different names, pipe, into, as, peg, chain, ...). Note that this let-abstraction is useful not only for hashes, so add it to the class Object.
Is there a `pipe` equivalent in ruby?
if you put your hashes in a array you could use the map function to convert the entries
I can't think of anything that will do this magically, since you're essentially wanting to remap an arbitrary data structure.
Something you may be able to do is:
require 'pp'
original_hash = {
:name=>'abc',
:school => {
:name=>'school name'
},
:testScores => [1,2,3,4,5]
}
result = {}
original_hash.each {|k,v| v.is_a?(Hash) ? v.each {|k1,v1| result[ [k.to_s, k1.to_s].join('_') ] = v1 } : result[k] = v}
result # {:name=>"abc", "school_name"=>"school name", :testScores=>[1, 2, 3, 4, 5]}
but this is incredibly messy and I'd personally be unhappy with it. Performing a manual transform on known keys is probaby better and more maintainable.
Check Facets Hash extensions.

Refactor ruby on rails model

Given the following code,
How would you refactor this so that the method search_word has access to issueid?
I would say that changing the function search_word so it accepts 3 arguments or making issueid an instance variable (#issueid) could be considered as an example of bad practices, but honestly I cannot find any other solution. If there's no solution aside from this, would you mind explaining the reason why there's no other solution?
Please bear in mind that it is a Ruby on Rails model.
def search_type_of_relation_in_text(issueid, type_of_causality)
relation_ocurrences = Array.new
keywords_list = {
:C => ['cause', 'causes'],
:I => ['prevent', 'inhibitors'],
:P => ['type','supersets'],
:E => ['effect', 'effects'],
:R => ['reduce', 'inhibited'],
:S => ['example', 'subsets']
}[type_of_causality.to_sym]
for keyword in keywords_list
relation_ocurrences + search_word(keyword, relation_type)
end
return relation_ocurrences
end
def search_word(keyword, relation_type)
relation_ocurrences = Array.new
#buffer.search('//p[text()*= "'+keyword+'"]/a').each { |relation|
relation_suggestion_url = 'http://en.wikipedia.org'+relation.attributes['href']
relation_suggestion_title = URI.unescape(relation.attributes['href'].gsub("_" , " ").gsub(/[\w\W]*\/wiki\//, ""))
if not #current_suggested[relation_type].include?(relation_suggestion_url)
if #accepted[relation_type].include?(relation_suggestion_url)
relation_ocurrences << {:title => relation_suggestion_title, :wiki_url => relation_suggestion_url, :causality => type_of_causality, :status => "A", :issue_id => issueid}
else
relation_ocurrences << {:title => relation_suggestion_title, :wiki_url => relation_suggestion_url, :causality => type_of_causality, :status => "N", :issue_id => issueid}
end
end
}
end
If you need additional context, pass it through as an additional argument. That's how it's supposed to work.
Setting #-type instance variables to pass context is bad form as you've identified.
There's a number of Ruby conventions you seem to be unaware of:
Instead of Array.new just use [ ], and instead of Hash.new use { }.
Use a case statement or a constant instead of defining a Hash and then retrieving only one of the elements, discarding the remainder.
Avoid using return unless strictly necessary, as the last operation is always returned by default.
Use array.each do |item| instead of for item in array
Use do ... end instead of { ... } for multi-line blocks, where the curly brace version is generally reserved for one-liners. Avoids confusion with hash declarations.
Try and avoid duplicating large chunks of code when the differences are minor. For instance, declare a temporary variable, conditionally manipulate it, then store it instead of defining multiple independent variables.
With that in mind, here's a reworking of it:
KEYWORDS = {
:C => ['cause', 'causes'],
:I => ['prevent', 'inhibitors'],
:P => ['type','supersets'],
:E => ['effect', 'effects'],
:R => ['reduce', 'inhibited'],
:S => ['example', 'subsets']
}
def search_type_of_relation_in_text(issue_id, type_of_causality)
KEYWORDS[type_of_causality.to_sym].collect do |keyword|
search_word(keyword, relation_type, issue_id)
end
end
def search_word(keyword, relation_type, issue_id)
relation_occurrences = [ ]
#buffer.search(%Q{//p[text()*= "#{keyword}'"]/a}).each do |relation|
relation_suggestion_url = "http://en.wikipedia.org#{relation.attributes['href']}"
relation_suggestion_title = URI.unescape(relation.attributes['href'].gsub("_" , " ").gsub(/[\w\W]*\/wiki\//, ""))
if (!#current_suggested[relation_type].include?(relation_suggestion_url))
occurrence = {
:title => relation_suggestion_title,
:wiki_url => relation_suggestion_url,
:causality => type_of_causality,
:issue_id => issue_id
}
occurrence[:status] =
if (#accepted[relation_type].include?(relation_suggestion_url))
'A'
else
'N'
end
relation_ocurrences << occurrence
end
end
relation_occurrences
end

Help with transforming arrays and hashes

I'm trying to use Highcharts in my web app, but I'm having some trouble getting my arrays to match the output that highcharts needs, essentially hightcharts needs output like this in it's JS:
series: [{
name: 'person',
data: [1562, 873, 1457]
}, {
name: 'car',
data: [7323, 324, 1233]
}, {
name: 'SUV',
data: [832, 6232, 4432]
}]
Where each item in the data array in this case is a new day, my problem is that my data is organized a bit differently, and trying to transform my data to match this has been a headache. basically my output is like so:
[
{:date=>"Sun, 10 Apr 2011", :object_types=>[
{:count=>4279, :class_name=>"person"},
{:count=>8785, :class_name=>"car"},
{:count=>2153, :class_name=>"SUV"}
]},
{:date=>"Sat, 09 Apr 2011", :object_types=>[
{:count=>12206, :class_name=>"person"},
{:count=>29095, :class_name=>"car"},
{:count=>7565, :class_name=>"SUV"}
]},
{:date=>"Fri, 08 Apr 2011", :object_types=>[
{:count=>4159, :class_name=>"person"},
{:count=>8043, :class_name=>"car"},
{:count=>1982, :class_name=>"SUV"}
]}
]
So it seems that I need to primarily have the items first grouped by (in my case) :class_name, then within that the :count by day as each item in data.
I'm having trouble find the the "ruby way" to do this elegantly, I'm not super awesome at manipulating arrays and hashes as you can probably see, but any bit of help in guiding me towards accomplishing this the right way is appreciated.
Secondly, I tried to accomplish this using group() in my query originally but that doesnt seem to be helping much, thoughts? Another thing I'll have to solve is filling dates with "0" if there are no records for it... but I'll tackle that next I think.
I would use Enumerable#group_by, because that's the harder part; the rest is just basic transformation:
r = data.flat_map{|h| h[:object_types]}.
group_by{|h| h[:class_name]}.
map do |class_name, hashes|
{:name => class_name, :data => hashes.map{|h| h[:count]}}
end
Note: group_by is in ActiveRecord or Ruby 1.8.7+. flat_map is new to Ruby 1.9.2. Use the right version, or require "backports".
Something like this should work:
def convert_data(input)
hash = {}
input.each do |o|
o[:object_types].each do |ot|
if hash[ot[:class_name]]
hash[ot[:class_name]] << ot[:count]
else
hash[ot[:class_name]] = [ot[:count]]
end
end
end
hash.map {|k,v| {'name' => k, 'data' => v}}
end
You can call this function with your input data and you've got an object which fits the format you expect for the Highcharts library.
aux = Hash.new(Array.new)
series = Array.new
# After this code the aux hash will look something like this: {:person => [4159, 1231, 255], :suv => [1231, 4123, 5411], :car => [321, 312, 541]}
raw_array.each do |date_hash|
date_hash[:object_types].each do |inf|
aux[inf[:class_name]] << inf[:count]
end
end
aux.each { |k,v| series << {:name => k.to_s, :data => v} }
Then that series array is what you need. Just render it like json.

Ruby: Create hash with default keys + values of an array

I believe this has been asked/answered before in a slightly different context, and I've seen answers to some examples somewhat similar to this - but nothing seems to exactly fit.
I have an array of email addresses:
#emails = ["test#test.com", "test2#test2.com"]
I want to create a hash out of this array, but it must look like this:
input_data = {:id => "#{id}", :session => "#{session}",
:newPropValues => [{:key => "OWNER_EMAILS", :value => "test#test.com"} ,
{:key => "OWNER_EMAILS", :value => "test2#test2.com"}]
I think the Array of Hash inside of the hash is throwing me off. But I've played around with inject, update, merge, collect, map and have had no luck generating this type of dynamic hash that needs to be created based on how many entries in the #emails Array.
Does anyone have any suggestions on how to pull this off?
So basically your question is like this:
having this array:
emails = ["test#test.com", "test2#test2.com", ....]
You want an array of hashes like this:
output = [{:key => "OWNER_EMAILS", :value => "test#test.com"},{:key => "OWNER_EMAILS", :value => "test2#test2.com"}, ...]
One solution would be:
emails.inject([]){|result,email| result << {:key => "OWNER_EMAILS", :value => email} }
Update: of course we can do it this way:
emails.map {|email| {:key => "OWNER_EMAILS", :value => email} }

Resources