Using code block as a value in a parent hash - ruby

I am trying to refactor my code (before it was a bunch of if/else-if statements) using some Ruby magic. However, when I try to use code blocks as my value in a hash, I get the following error:
syntax error, unexpected '}', expecting tASSOC :restaurant => { Cuisine.all },
This same error repeats for the next line and the line after (:hotel and :attraction).
What am I doing incorrectly here?
def moderation_categories(klass)
klass_map = {
:restaurant => { Cuisine.all },
:hotel => { Category.where(place_type: "Hotel") },
:attraction => { Category.where(place_type: "Attraction") }
}
list = []
klass_map[klass.to_sym].call.each { |c| list << c.name }
list.sort
end

A block does not just stand as a lambda (code block). You must specify it to be a lambda or a Proc.
klass_map = {
:restaurant => lambda{ Cuisine.all },
:hotel => lambda{ Category.where(place_type: "Hotel") },
:attraction => lambda{ Category.where(place_type: "Attraction") }
}

Related

Ruby print or return specific field from object

How do I print the group_id from the returned object?
The following is returned from a function. I want to print the group_id or maybe return the group_id
{
:security_groups=>[
{
:description=>"Created By ManageIQ",
:group_name=>"MIQ_019",
:ip_permissions=>[
{
:from_port=>22,
:ip_protocol=>"tcp",
:ip_ranges=>[
{
:cidr_ip=>"0.0.0.0/0",
:description=>nil
}
],
:ipv_6_ranges=>[],
:prefix_list_ids=>[],
:to_port=>22,
:user_id_group_pairs=>[]
}
],
:owner_id=>"943755119718",
:group_id=>"sg-0c2c5f219f1bafc1a",
:ip_permissions_egress=>[
{
:from_port=>nil,
:ip_protocol=>"-1",
:ip_ranges=>[
{
:cidr_ip=>"0.0.0.0/0",
:description=>nil
}
],
:ipv_6_ranges=>[],
:prefix_list_ids=>[],
:to_port=>nil,
:user_id_group_pairs=>[]
}
],
:tags=>[],
:vpc_id=>"vpc-d817c1b3"
}
],
:next_token=>nil
}
This is the function: I want to return security_group.group_id
def describe_security_group (
group_name
)
ec2 = get_aws_client
security_group = ec2.describe_security_groups(
filters: [
{name: 'group-name', values: [ group_name ]}]
)
puts "Describing security group '#{group_name}' with ID " \
"'#{security_group}'"
return security_group
rescue StandardError => e
puts "Error describing security group: #{e.message}"
return
end
So, returning value seems like a hash, or you can make it hash exactly.
For case with one-element array you can simple use ruby dig method.
And according to your datum and comment below we can access needed element like this:
# from your ec2 api call
security_group = ec2.describe_security_groups(...)
# Result value is stored in `security_group` variable,
# and looks exactly like hash below
{
:security_groups=>[
{
:description=>"Created By ManageIQ",
:group_name=>"MIQ_019",
:ip_permissions=>[
{
:from_port=>22,
:ip_protocol=>"tcp",
:ip_ranges=>[
{
:cidr_ip=>"0.0.0.0/0",
:description=>nil
}
],
:ipv_6_ranges=>[],
:prefix_list_ids=>[],
:to_port=>22,
:user_id_group_pairs=>[]
}
],
:owner_id=>"943755119718",
:group_id=>"sg-0c2c5f219f1bafc1a",
:ip_permissions_egress=>[
{
:from_port=>nil,
:ip_protocol=>"-1",
:ip_ranges=>[
{
:cidr_ip=>"0.0.0.0/0",
:description=>nil
}
],
:ipv_6_ranges=>[],
:prefix_list_ids=>[],
:to_port=>nil,
:user_id_group_pairs=>[]
}
],
:tags=>[],
:vpc_id=>"vpc-d817c1b3"
}
],
:next_token=>nil
}
# And this is a target value, that you can store in another one,
# return from method or simply print to output
security_group.dig(:security_groups)
.try(:[], 0)
.dig(:group_id)
=> "sg-0c2c5f219f1bafc1a"
But if you need to search in array with multiple elements, methods from Ruby's Enumerable module could be helpful (like select or reject).
UPDATE with OpenStruct, if you prefer such method calls with dot notation:
json = security_group.to_json
os = JSON.parse(json, object_class: OpenStruct)
os.security_groups.first.group_id
=> "sg-0c2c5f219f1bafc1a"

How to loop inside a hash?

If I have a hash like the one below, and I want to loop over the second level keys.
Why does this fail?
hash["Element"].each do |id|
h[id] = hash[id]["Name"]
end
hash
{
"Element" => {
"499723" => {
"Name" => "A",
},
"499725" => {
"Name" => "B",
},
}
It fails because using .each on a Hash yields the tuple of key and value.
hash = {
"Element" => {
"499723" => {
"Name" => "A",
},
"499725" => {
"Name" => "B",
},
}
}
hash["Element"].each do |id|
p id
end
["499723", {"Name"=>"A"}]
["499725", {"Name"=>"B"}]
Therefore, you need to use
hash["Element"].each do |id, value|
# ...
end
If you don't need the value
hash["Element"].each do |id, _|
# ...
end
However, you can keep the value and access it directly
hash["Element"].each do |id, value|
h[id] = value["Name"]
end
A simple solution in your case is to use Enumberable#each_with_object in combination with the previous information:
hash["Element"].each_with_object({}) do |(id, value), acc|
acc[id] = value["Name"]
end
# => {"499723"=>"A", "499725"=>"B"}
hash["Element"].each.with_object({}) do |(id, subhash), result|
result[id] = subhash["Name"]
end

How to specify type of JSON value?

I'm using Grape on Padrino to make a test API for my mobile app.
How can I specify the type of my JSON object?
Here is how i do it, but every returned value is a String:
module Acme
module Api
class Ping < Grape::API
format :json
get '/user/112132a08s245c/availability_list' do
{
"availability_list"=> [
{
:type=> "OOO",
:from_date=> "21-12-2004",
:to_date=> "21-23-2007",
:all_day=> "false"
},
{
:type=> "WFH",
:from_date=> "21-12-2004",
:to_date=> "21-23-2007",
:all_day=> "false"
}
]
}
end
get '/user/112132a08s245c/issues' do
{
"issues"=> [
{
:issure_id=> "1ab300co221",
:title=> "No water",
:description=> "No water in kitchen",
:severity=> "low",
"location" => {
:lat => "37.4224764",
:lng => "-122.0842499"
}
},
{
:issure_id=> "1ab300co222",
:title=> "No fire",
:description=> "No fire in kitchen",
:severity=> "low",
"location" => {
:lat => "37.4224764",
:lng => "-122.0842499"
}
}
]
}
end
end
end
Meditate on this:
require 'json'
foo = {'a' => 1}
foo.class # => Hash
str = JSON[foo] # => "{\"a\":1}"
str.class # => String
bar = JSON[str] # => {"a"=>1}
bar.class # => Hash
You need to read the JSON spec. JSON serializes data into a string because objects can't be transferred between disparate languages. When it sees an object the parser serializes it into a string. When the incoming string is received and passed on to the parser, it knows it has to convert the string back to an object.

Ruby mongoid aggregation return object

I am doing an mongodb aggregation using mongoid, using ModleName.collection.aggregate(pipeline) . The value returned is an array and not a Mongoid::Criteria, so if a do a first on the array, I get the first element which is of the type BSON::Document instead of ModelName. As a result, I am unable to use it as a model.
Is there a method to return a criteria instead of an array from the aggregation, or convert a bson document to a model instance?
Using mongoid (4.0.0)
I've been struggling with this on my own too. I'm afraid you have to build your "models" on your own. Let's take an example from my code:
class Searcher
# ...
def results(page: 1, per_page: 50)
pipeline = []
pipeline <<
"$match" => {
title: /#{#params['query']}/i
}
}
geoNear = {
"near" => coordinates,
"distanceField" => "distance",
"distanceMultiplier" => 3959,
"num" => 500,
"spherical" => true,
}
pipeline << {
"$geoNear" => geoNear
}
count = aggregate(pipeline).count
pipeline << { "$skip" => ((page.to_i - 1) * per_page) }
pipeline << { "$limit" => per_page }
places_hash = aggregate(pipeline)
places = places_hash.map { |attrs| Offer.new(attrs) { |o| o.new_record = false } }
# ...
places
end
def aggregate(pipeline)
Offer.collection.aggregate(pipeline)
end
end
I've omitted a lot of code from original project, just to present the way what I've been doing.
The most important thing here was the line:
places_hash.map { |attrs| Offer.new(attrs) { |o| o.new_record = false } }
Where both I'm creating an array of Offers, but additionally, manually I'm setting their new_record attribute to false, so they behave like any other documents get by simple Offer.where(...).
It's not beautiful, but it worked for me, and I could take the best of whole Aggregation Framework!
Hope that helps!

Link_to with additional variable

I want to create a simple link_to (rails 3) with two additional variables:
= link_to 'Try', new_try_path(:k => users.collect{|m| m.user.username}, :h=> users2.collect{|m| m.user2.username2}, :proof => true)
The problem is if users2 is blank, this html code is generated: &k=[1]&&proof=true
I tried something like this. Can you help me please?
= link_to 'Try', new_try_path(:k => users.collect{|m| m.user.username}, :h=> users2.collect{|m| m.user2.username2} if users2.blank?, :proof => true)
Thank you!
Things like this should definitely be refactored into a helper, such as
# view
= try_link(users, users2)
# helper
def try_link(users, users2)
options = { :k => users.collect { |m| m.user.username }, :proof => true }
unless users2.blank?
options[:h] = users2.collect { |m| m.user2.username2 }
end
link_to 'Try', new_try_path(options)
end
This is about the bare minimum you can do to make the view code less horrible.
You might also want to consider putting the whole collect thing into the model.
Also Hash#merge might be helpful in cases like this, where you can do
a = { :foo => 1 }
b = { :bar => 2 }
puts a.merge(b) # => { :foo => 1, :bar => 2 }
Not very elegant, but should work:
- options = { :k => users.map{ |m| m.user.username }, :proof => true }
-# add :h parameter only if users2 is not empty
- options[:h] = users2.map{ |m| m.user2.username2 } unless users2.blank?
= link_to 'Try, new_try_path(options)
If users2 is blank h parameter will be omitted from generated URL.
As alternative you can filter out blank values from options hash:
# for ruby 1.9 (select only non-blank values)
options.select! { |k, v| v.present? }
# for ruby 1.8 (delete blank values)
options.delete_if { |k, v| v.blank? }

Resources