Ruby - Extract value of a particular key from array of hashes - ruby-on-rails

I have an array of hashes - #profiles which has data as:
[{:user_id=>5, :full_name=>"Emily Spot"},{:user_id=>7, :full_name=>"Kevin Walls"}]
I want to get full_name of say user_id = 7? I'm doing the following: but it's throwing an error that expression #profiles.find{|h| h[':user_id'] == current_user.id} is nil.
name = #profiles.find{ |h| h[':user_id'] == current_user.id }[':full_name']
if I use select instead of find then error is - no implicit conversion of String into Integer.
How do I search through the array of hashes?
UPDATE:
After #Eric's answer, I restructured my job model & view actions:
def full_names
profile_arr||= []
profile_arr = self.applications.pluck(:user_id)
#profiles = Profile.where(:user_id => profile_arr).select([:user_id, :first_name, :last_name]).map {|e| {user_id: e.user_id, full_name: e.full_name} }
#full_names = #profiles.each_with_object({}) do |profile, names|
names[profile[:user_id]] = profile[:full_name]
end
end
In the view....,
p #current_job.full_names[current_user.id]

#profiles is an array of hashes, with symbols as keys, whereas what you use is String objects.
So ':user_id' is a string, and you want symbol: :user_id:
#profiles.find{ |h| h[:user_id] == current_user.id }
I want to get full_name of say user_id == 7
#profiles.find { |hash| hash[:user_id] == 7 }.fetch(:full_name, nil)
Note, I used Hash#fetch for case, when there is no hash with value 7 at key :user_id.

As you've noticed, it's not very convenient to extract the name of user_id 7. You could modify your data structure a bit :
#profiles = [{:user_id=>5, :full_name=>"Emily Spot"},
{:user_id=>7, :full_name=>"Kevin Walls"}]
#full_names = #profiles.each_with_object({}) do |profile, names|
names[profile[:user_id]] = profile[:full_name]
end
p #full_names
# {5=>"Emily Spot", 7=>"Kevin Walls"}
p #full_names[7]
# "Kevin Walls"
p #full_names[6]
# nil
You didn't lose any information but name look-up is now much faster, easier and more robust.

Suggesting, to create a new hash that can make things simpler
Eg:
results = {}
profiles = [
{user_id: 5, full_name: "Emily Spot"},
{user_id: 7, full_name: "Kevin Walls"}
]
profiles.each do |details|
results[details[:user_id]] = details[:full_name]
end
Now, results will have:
{5: "Emily Spot", 7: "Kevin Walls"}
So, if you need to get full_name of say user_id = 7, simply do:
results[7] # will give "Kevin Walls"

Related

How to push/shovel/append active record relation correctly?

I have a fairly complex search form that allows for multiple duplicate nested fields to be submitted at once. I generate unique id's on clone so i can separate them using jquery. I then iterate over the duplicates and do something like the following:
So i have something like:
{..., "search"=>{"searches"=>{}, "searches_0"=>{}...{other_attributes}}
def search_post
search = []
params.each do |k, v|
search << do_search(params, v)
end
end
def do_search(search, v)
search_array = []
search = Model.where() if v[:this_param].present?
search = Model.where() if v[:that_param].present?
# it will be only one of the `search` as this_param or that_param can't be searched
together
search_array << search.where(attr: search[:attr]) if attr.present?
search_array << search.where(attr_2: search[:attr_2]) if attr_2.present?
search_array << search.where(attr_3 search[:attr_3]) if attr_3.present?
search_array.uniq
end
This gives a result like:
[#<ActiveRecord::Relation [#<LineItem id: 15, created_at: "2020-01-03 15:49:19", updated_at: "2020-01-03 15:49:19", ...>]>, #<ActiveRecord::Relation [#<LineItem id: 14, created_at: "2020-01-03 15:49:19", updated_at: "2020-01-03 15:49:19", ...>]>]
I obviously get an array but I need to do more queries on it.
I have tried using search.reduce([], :concat).uniq but this only removes all of the results and only keeps the ActiveRecord::Relation aspect of the array.
What I need is to shovel the results from the loop and be able to use where on it.
How can this be accomplished?
By the looks of it you can try using a query object:
class ModelQuery
def initialize(initial_scope = Model.all)
#initial_scope = initial_scope
end
def call(params)
scoped = by_this_param(initial_scope, params[:this_param])
scoped = by_that_param(initial_scope, params[:that_param])
scoped = by_other_param(initial_scope, params[:other])
# ...
scoped
end
def by_this_param(s, this_param = nil)
this_param ? s.where(this_attr: this_param) : s
end
def by_that_param(s, that_param = nil)
that_param ? s.where(that_attr: that_param) : s
end
# ...
def by_other(s, other = nil)
other ? s.where(other_attr: other) : s
end
end
You can then do things like:
q = ModelQuery.new
# or perhaps...
# q = ModelQuery.new(Model.where(whatever: "however"))
q.call params
q.order(whatever_column: :asc).group(:however)
Obviously you need to adapt and extend the code above to your variable names/parameters. Another upside of using the query object pattern is that it gives you a subtle nudge to structure your parameters coherently so you can pass from view and return from AR quickly, without much fiddling about with the input/output.
Give it a try!

Remove element from an array of objects and create array of hash

Rewriting the question with detailed example:
I have array of objects with three property, I would like to build array of harsh and have the resulting hash not have #name instance property. I have simulated the problem with an example.
example creation
class Employee
attr_accessor :name, :company, :duration
def initialize(name, company, duration)
#name = name
#company = company
#duration = duration
end
end
aSong1 = Employee.new("Fleck", "AMZ", 260)
aSong2 = Employee.new("Taylor", "EMC", 120)
aSong3 = Employee.new("Bob", "Adobe", 260)
aSong4 = Employee.new("Jack", "Google", 360)
final_array = [ ]
final_array.push(aSong1)
final_array.push(aSong2)
final_array.push(aSong3)
final_array.push(aSong4)
controller
puts final_array.length #4
final_array.each do | element |
puts element.is_a?(Object) #true
puts element.name #prints name
end
resulting array ( expected )
result = [{company: 'AMZ',duration: 260}, {company: 'EMC',duration: 120},{company: 'Adobe',duration: 260}, {company: 'Google',duration: 360} ]
Example: repl
You can delete the id keys using
a = [{"id":"21","company":"AMC","name":"Matt"},{"id":"22","company":"AMC","name":"Jon"},
{"id":"12","company":"XYZ","name":"Bob"}].each{|o| o.delete :id}
If you want to compare it with other hashes you can use the merge method assuming a2 is the second Hash
a.merge(a2)
This will return a new hash with any matching keys updated with new corresponding values from the second hash
final_array.collect {|x| { company: x.company, duration: x.duration }}
result:
[{:company=>"AMZ", :duration=>260}, {:company=>"EMC", :duration=>120}, {:company=>"Adobe", :duration=>260}, {:company=>"Google", :duration=>360}]
How about the following?
class Employee
def to_h
instance_variables.each_with_object({}) do |var, h|
k = var.to_s.tr('#','').to_sym
v = instance_variable_get(var)
h[k] = v
end
end
end
result = final_array.map{|e| e.to_h.delete_if{|k,v| k == :name}}

De duplication of hashes and arrays

I have an array of hashes lets say offers = {offer1,...,offer6}. Each offer is a hash.
Each offer has various keys one of which is a price hash:
offer1 = {..., :price => {:amount => 400, :amount2 => 300, :currency => "INR",...}
Now I want to return a hash with only the unique hashes/offers. Also, I want :price hash to have the values averaged.
So, the final offers array will have:
offers=[offer1, offer2,...]
offer1[:price] will have values equal to average of the values of each key from duplicated offer hashes and same with offer2[:price] and so on so that in end I end with an hash with only unique offers.
offers1..6 can be duplicate with same id and price hash different. If they are duplicate we need to do the averaging otherwise not.
Is there an elegant way to do all of this?
I have tried grouping the hashes with a unique key and merging the price hashes of each. But am unable to reach the final solution.
my attempt:
rooms_hash = rooms.map do |room|
unless room[:offers].uniq.count == room[:offers].count
grouped_offers = room[:offers].group_by{|x| x[:room_category_id]}
offer_values = grouped_offers.values
price_array = offer_values.map do |v|
v.inject do |k, v|
k.merge!(price: k[:price].merge(v[:price]){|_, a, b| [a, b].flatten })
end
end
price_array.map do |o|
o[:price] = {}.tap{ |h| o[:price].each {|k, list| h[k] = list.all?{|e| [Fixnum, NilClass].include? e.class} ? list.map(&:to_i).sum/list.size : list.compact.first ; h } }
end
price_array
end
end
rooms.zip(rooms_hash).map do |room,averaged_offers|
if room[:offers].count > 1
room[:offers] = averaged_offers.select{|offer| offer[:room_category_id] == room[:room_category_id]}
room
end
end
This code actually fails when I don't get any duplicates. So how can I check for that as well?
Edit:
Yes offers is an array and offers1..6 are hashes.
offer1..._hash = {:offer_id=>"uuid", :price=>{:amount=>4422380, :gross_amount=>4422380, :currency=>"INR", :tax=>434318, :hotel_fees=>0, :base_fare=>3988062}, .....}

Rails: each for multiple and one object data

I'm new in rails and need to clear one question:
for example my method return such data:
#<Article ART_ID: 1151754, ART_ARTICLE_NR: "0 281 002 757", ART_SUP_ID: 30, ART_DES_ID: nil, ART_COMPLETE_DES_ID: 62395, ART_CTM: nil, ART_PACK_SELFSERVICE: 0, ART_MATERIAL_MARK: 0, ART_REPLACEMENT: 0, ART_ACCESSORY: 0, ART_BATCH_SIZE1: nil, ART_BATCH_SIZE2: nil, datetime_of_update: "2012-09-25 17:49:18">
or array, not only one object: how could use each func then?
for example:
articles = ArtLookup.search_strong_any_kind_without_brand(params[:article_nr].gsub(/[^0-9A-Za-z]/, ''))
binding.pry
if articles.present?
articles.each do |a|
#all_parts_result <<
{
analogue_manufacturer_name: a.supplier.SUP_BRAND,
analogue_code: a.ART_ARTICLE_NR,
delivery_time_min: '',
delivery_time_max: '',
min_quantity: '',
product_name: a.art_name,
quantity: '',
price: '',
distributor_id: '',
link_to_tecdoc: a.ART_ID
}
end
end
now i get errors like
`undefined method `each' for `#<Article:0x007f6554701640>
i think it is becouse i have sometimes one object, sometimes 10, and sometime 0.
how is it beatifull and right to do in rails?
Your search_strong_any_kind_without_brand method is looping through your articles based on the search condition. If the article matches then you are setting #art_concret to the match and then returning the match. However, you're not finding all matches, just the last one.
.
loop
#art_concret = art
end
.
return #art_concret
If you set the #art_concret as an array and inject results into this instance variable, then you will have the resulting search in array form. However, keep in mind that this does kind of break the ActiveRecord ORM as you would be returning a simple array and not an ActiveRecord Relation array.
def self.search_strong_any_kind_without_brand(search)
search_condition = search.upcase
#art_concret = []
#search = find(:all, :conditions => ['MATCH (ARL_SEARCH_NUMBER) AGAINST(? IN BOOLEAN MODE)', search_condition])
#articles = Article.find(:all, :conditions => ["ART_ID in (?)", #search.map(&:ARL_ART_ID)])
#binding.pry
#articles.each do |art|
if art.ART_ARTICLE_NR.gsub(/[^0-9A-Za-z]/, '') == search
#art_concret << art
end
end
return #art_concret
end
If you want to keep the code a bit cleaner then use select on your matching condition instead of looping through each article in #articles.
def self.search_strong_any_kind_without_brand(search)
search_condition = search.upcase
#search = find(:all, :conditions => ['MATCH (ARL_SEARCH_NUMBER) AGAINST(? IN BOOLEAN MODE)', search_condition])
#articles = Article.find(:all, :conditions => ["ART_ID in (?)", #search.map(&:ARL_ART_ID)])
#binding.pry
return #articles.select { |art| art.ART_ARTICLE_NR.gsub(/[^0-9A-Za-z]/, '') == search }
end
Unrelated: is there a reason why you're using instance variables in search_strong_any_kind_without_brand?
I think the right thing to do is to make sure your method always returns an array (or enumerable).
looking at the code you posted in to pastebin I would recommend you use Array#select in your method
for example you might be able to just return this:
#articles.select { |art| art.ART_ARTICLE_NR.gsub(/[^0-9A-Za-z]/, '') == search }
assuming #articles is an array or collection you will always get an array back, even if it is 0, or 1 element
This answer would be a bit offtopic, but I would like to mention a splat operator:
[*val]
will produce array, consisting of either single val value whether it’s not an array, or the array itself whether val is an array:
▶ def array_or_single param
▷ [*param].reduce &:+ # HERE WE GO
▷ end
=> :array_or_single
▶ array_or_single [1,2,3]
=> 6
▶ array_or_single 5
=> 5
That said, you code would work with this tiny improvement:
- articles.each do |a|
+ [*articles].each do |a|
Hope it gives a hint on how one might handle the data, coming from the 3rd party. As an answer to your particular question, please follow the advises in the other answers here.

Rails get multiple values from db

I'm trying to retrieve multiple values from a database into a single variable and return the whole thing. Here is what I am doing
my_hash = {
'name' => 'John'
'current_location' => 'Sweden'
}
Now I need to go into database and retrieve all records and store them into a single variable, and then i need to add that variable into my_hash so I can return the whole thing. How would I do that?
Example:
last_names = Names.where('first_name = ?', 'John').select('last_name').last_name
my_hash.add(last_names)
return my_hash
Now that above does not works, can somebody tell me proper way to achieve this?
are you trying to do the following?
my_hash = {
'name' => 'John'
'current_location' => 'Sweden'
}
my_hash['last_names'] = Names.where('first_name = ?', 'John')
.select('last_name')
.map { |name| name.last_name }
# or shorthand version .map(&:last_name)
return my_hash
Updated
# get name objects from the database
# add select as an optimization if desired
last_name_list = Names.where('first_name = ?', 'John')
# get an array of only the last_name fields
last_names = last_name_list.map { |name| name.last_name }
# assign the array to the new hash key 'last_names'
my_hash['last_names'] = last_names
see http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-map for documentation on map, note that map and collect are the same
another example
names = Names.where('updated_at >= ?', Date.parse('2013-01-01'))
# get an array of 'full names'
full_names = names.map do |name|
"#{name.first_name} #{name.last_name}"
end

Resources