using rescue in variable assignment - ruby

I have this
User.new ( gender: auth.extra.raw_info.gender.capitalize, ...)
auth is a hash that looks like this
auth => {:extra => { :raw_info => { :gender => ... , .. }, ..} ..}
sometimes, for whatever reason, the gender doesn't exist and I want to have a default value for it when I create a new user
If I try
gender: auth.extra.raw_info.gender.try(:capitalize) || "Male"
but the gender itself doesn't exist, and I can't try on gender
using gender: auth.extra.raw_info.gender.capitalize rescue "Male"
also doesn't work since it says I can't use capitalize on nil (which is gender)
Is there a way to do this without using a variable for this (since it will become messier)

I think the standard way to do this is to use reverse_merge:
auth.reverse_merge! {:extra => { :raw_info => { :gender => "Male" } } }

Related

How to organize hashes by property

Here is the hashes that will be processed:
{
"flatiron school bk" => {
:location => "NYC"
},
"flatiron school" => {
:location => "NYC"
},
"dev boot camp" => {
:location => "SF"
},
"dev boot camp chicago" => {
:location => "Chicago"
},
"general assembly" => {
:location => "NYC"
},
"Hack Reactor" => {
:location => "SF"
}
}
I need to organize these hashes by location, like this:
{ "NYC"=>["flatiron school bk", "flatiron school", "general assembly"],
"SF"=>["dev boot camp", "Hack Reactor"],
"Chicago"=>["dev boot camp chicago"]}
}
You can use each_with_object to combine in into new hash:
hash.each_with_object({}) do |(name, data), res|
(res[data[:location]] ||= []) << name
end
Explanation:
each_with_object
Iterates the given block for each element with an arbitrary object given, and returns the initially given object.
In this case name and data is key and value of each element in given hash.
In (res[data[:location]] ||= []) << name you get location, create array in result hash for given location (if it doesn't exist), then put key of input hash to it.

How to use a string description to access data from a hash-within-hash structure?

I have the following:
data_spec['data'] = "some.awesome.values"
data_path = ""
data_spec['data'].split('.').each do |level|
data_path = "#{data_path}['#{level}']"
end
data = "site.data#{data_path}"
At this point, data equals a string: "site.data['some']['awesome']['values']"
What I need help with is using the string to get the value of: site.data['some']['awesome']['values']
site.data has the following value:
{
"some" => {
"awesome" => {
"values" => [
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
}
}
}
Any help is greatly appreciated. Thanks!
You could do as tadman suggested and use site.data.dig('some', 'awesome', values') if you are using ruby 2.3.0 (which is awesome and I didn't even know existed). This is probably your best choice. But if you really want to write the code yourself read below.
You were on the right track, the best way to do this is:
data_spec['data'] = "some.awesome.values"
data = nil
data_spec['data'].split('.').each do |level|
if data.nil?
data = site.data[level]
else
data = data[level]
end
end
To understand why this works first you need to understand that site.data['some']['awesome']['values'] is the same as saying: first get some then inside that get awesome then inside that get values. So our first step is retrieving the some. Since we don't have that first level yet we get it from site.data and save it to a variable data. Once we have that we just get each level after that from data and save it to data, allowing us to get deeper and deeper into the hash.
So using your example data would initally look like this:
{"awesome" => {
"values" => [
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
}
}
Then this:
{"values" => [
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
}
and finally output like this:
[
{
"things" => "Stuff",
"stuff" => "Things",
},
{
"more_things" => "More Stuff",
"more_stuff" => "More Things",
}
]
If you're receiving a string like 'x.y.z' and need to navigate a nested hash, Ruby 2.3.0 includes the dig method:
spec = "some.awesome.values"
data = {
"some" => {
"awesome" => {
"values" => [
'a','b','c'
]
}
}
}
data.dig(*spec.split('.'))
# => ["a", "b", "c"]
If you don't have Ruby 2.3.0 and upgrading isn't an option you can just patch it in for now:
class Hash
def dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
I wrote something that does exactly this. Feel free to take any information of value from it or steal it! :)
https://github.com/keithrbennett/trick_bag/blob/master/lib/trick_bag/collections/collection_access.rb
Check out the unit tests to see how to use it:
https://github.com/keithrbennett/trick_bag/blob/master/spec/trick_bag/collections/collection_access_spec.rb
There's an accessor method that returns a lambda. Since lambdas can be called using the [] operator (method, really), you can get such a lambda and access arbitrary numbers of levels:
accessor['hostname.ip_addresses.0']
or, in your case:
require 'trick_bag'
accessor = TrickBag::CollectionsAccess.accessor(site.data)
do_something_with(accessor['some.awesome.values'])
What you are looking for is something generally looked down upon and for good reasons. But here you go - it's called eval:
binding.eval data

Adding hashes to hashes (Ruby)

I've tried adding hashes through #Hash.new with no success, now I am trying .merge as per the forums with limited success as well. I'm trying to add #rand(1..100) into [0] without going into the hash manually. Any ideas?
#age = Hash.new
#email = Hash.new
#age2 = rand(1..100)
people = [
{
"first_name" => "Bob",
"last_name" => "Jones",
"hobbies" => ["basketball", "chess", "phone tag"]
},
{
"first_name" => "Molly",
"last_name" => "Barker",
"hobbies" => ["programming", "reading", "jogging"]
},
{
"first_name" => "Kelly",
"last_name" => "Miller",
"hobbies" => ["cricket", "baking", "stamp collecting"]
}
]
people[0].each do |w|
people.merge({:age => rand(1..100)})
puts "array 0 is #{w}"
end
puts p people
Assuming that's your structure, you do this:
people.each do |person|
person['age'] = rand(1..100)
end
You ideally want to use symbol-style keys instead. That would mean declaring them like this:
people = [
{
first_name: "Bob",
last_name: "Jones",
...
},
...
]
That way you access them like people[0][:first_name]. Your merged in hash uses symbol keys for :age. Remember in Ruby strings and symbols are not equivalent, that is 'bob' != :bob. You should use symbols for regular structures like this, strings for more arbitrary data.

Update activerecord relation given a hash of multiple entries

I'm quite new to Rails, so be gentle :)
I have the following models set-up:
class User
has_many :it_certificates, :class_name => 'UserCertificate'
class UserCertificate
belongs_to :skill
Given the following input (in JSON)
{
"certificates":[
{ // update
"id":1,
"name":"Agile Web Dev 2",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2012
},
"skill": {
"id":57
}
},
{ // create
"name":"Agile Web Dev 1",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2011
},
"skill": {
"id":58
}
}
]
}
How's the easiest way to update the information for the relation it_certificates?
I've been looking to update_all but it doesn't match my needs (it only updates given fields with the same value).
So I've been struggling around with the approach of iterating over each of these records and then update them one-by-one.
I mean struggling because it looks to me there are lots of things I have to care of when the idea of Rails is the opposite.
Thanks in advance!
So, here's my solution for now:
def self.update_from_hash(data, user_id)
self.transaction do
data.each do |certificate|
if certificate[:id] == nil
# create
if !self.create(
:name => certificate[:name],
:entity => certificate[:entity],
:user_id => user_id,
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
)
raise ActiveRecord::Rollback
end
else
# update
if !self.update(certificate[:id], {
:name => certificate[:name],
:entity => certificate[:entity],
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
})
raise ActiveRecord::Rollback
end
end
end
end
return true
end
It works, but I'm still expecting a more elegant solution :)

How to validate a complete form with RSpec and Capybara?

I'm writing a request test with RSpec and Capybara. I have a hash that maps form field names to expected values.
How can I check easily that each form field has the expected value?
So far, I'm doing this, but it's complex and unmaintainable. I'm also considering only two kind of input controls in this case (select boxes and the rest):
expected_data = {
"address" => "Fake st 123",
"city" => "Somewhere",
"email" => "whoknows#example.com",
"gender" => "Male",
"state" => "FL",
}
select_boxes = ["gender", "state"]
# check for the select boxes
expected_data.select {|k,v| select_boxes.include?(k)}.each do |name, expected_value|
page.has_select?(name, :selected_value => expected_value).should == true
end
# check for the input fields
expected_data.reject {|k,v| select_boxes.include?(k)}.values.each do |expected_value|
page.should have_css("input[value=\"#{expected_value}\"]")
end
Is there a gem or something to do this in one line?
I find the following far more maintainable:
describe "form" do
subject {page}
before { visit "/path/to/form" }
it { should have_field("address", :with => "Fake st 123") }
it { should have_select("gender", :selected => "Male") }
# And so on ...
end

Resources