Looping over Ruby hash and accessing values - ruby-on-rails

I am quite new to Ruby and could not find an appropriate answer to my questions. Let's say I have hash named
users_hsh = {}.
I am looping through all of my users in the DB and creating the following.
users.each do |user|
users_hsh[user.full_name] = {
completed_activities: some_integer_value,
active_activities: some_integer_value,
future_activities: some_integer_value
}
end
Now, I created a new hash named
total_sum_not_zero_user_hsh = {}.
I want to loop over all of the users in the users_hsh and check for each user if the total sum of completed_activities + active_activities + future_activities does not equal 0 and if this condition holds, I want to add this user to total_sum_not_zero_user_hsh. I have done the following but seems that this does not work.
users_hsh.each do |usr|
if usr.values.sum != 0
total_sum_not_zero_user_hsh[usr] = {
completed_activities: some_integer_value,
active_activities: some_integer_value,
future_activities: some_integer_value
}
end
end
What am I doing wrong? Thanks in advance!

Let's use your example of:
users_hash = {
"Elvin Jafarli" => {
completed_activities: 10,
active_activities: 2,
future_activities: 0
}
}
Think carefully about what your data structure actually is: It's a hash that maps the user name to some user attributes. If you loop through these values, you don't just get a usr, you get back precisely this mapping.
It's helpful to name your variables descriptively:
users_hsh.each do |user_name, user_attributes|
if user_attributes.values.sum != 0
# ...
end
end
With your attempt, you would have seen an error like this: NoMethodError: undefined method 'values' for #<Array:0x00007fe14e22f538>. What happened is that each usr was actually an Array such as:
["Elvin Jafarli", {completed_activities: 10, active_activities: 2, future_activities: 0}]

Related

Mongoid Aggregate result into an instance of a rails model

Introduction
Correcting a legacy code, there is an index of object LandingPage where most columns are supposed to be sortable, but aren't. This was mostly corrected, but few columns keep posing me trouble.
Theses columns are the one needing an aggregation, because based on a count of other documents. To simplify the explanation of the problem, I will speak only about one of them which is called Visit, as the rest of the code will just be duplication.
The code fetch sorted and paginate data, then modify each object using LandingPage methods before sending the json back. It was already like this and I can't modify it.
Because of that, I need to do an aggregation (to sort LandingPage by Visit counts), then get the object as LandingPage instance to let the legacy code work on them.
The problem is the incapacity to transform Mongoid::Document to a LandingPage instance
Here is the error I got:
Mongoid::Errors::UnknownAttribute:
Message:
unknown_attribute : message
Summary:
unknown_attribute : summary
Resolution:
unknown_attribute : resolution
Here is my code:
def controller_function
landing_pages = fetch_landing_page
landing_page_hash[:data] = landing_pages.map do |landing_page|
landing_page.do_something
# Do other things
end
render json: landing_page_hash
end
def fetch_landing_page
criteria = LandingPage.where(archived: false)
columns_name = params[:columns_name]
column_direction = params[:column_direction]
case order_column_name
when 'visit'
order_by_visits(criteria, column_direction)
else
criteria.order_by(columns_name => column_direction).paginate(
per_page: params[:length],
page: (params[:start].to_i / params[:length].to_i) + 1
)
end
def order_by_visit(criteria, order_direction)
def order_by_visits(landing_pages, column_direction)
LandingPage.collection.aggregate([
{ '$match': landing_pages.selector },
{ '$lookup': {
from: 'visits',
localField: '_id',
foreignField: 'landing_page_id',
as: 'visits'
}},
{ '$addFields': { 'visits_count': { '$size': '$visits' }}},
{ '$sort': { 'visits_count': column_direction == 'asc' ? 1 : -1 }},
{ '$unset': ['visits', 'visits_count'] },
{ '$skip': params[:start].to_i },
{ '$limit': params[:length].to_i }
]).map { |attrs| LandingPage.new(attrs) { |o| o.new_record = false } }
end
end
What I have tried
Copy and past the hash in console to LandingPage.new(attributes), and the instance was created and valid.
Change the attributes key from string to symbole, and it still didn't work.
Using is_a?(hash) on any element of the returned array returns true.
Put it to json and then back to a hash. Still got a Mongoid::Document.
How can I make the return of the Aggregate be a valid instance of LandingPage ?
Aggregation pipeline is implemented by the Ruby MongoDB driver, not by Mongoid, and as such does not return Mongoid model instances.
An example of how one might obtain Mongoid model instances is given in documentation.

How to calculate specific rating count hash in ruby on rails?

So, I have an after_save hook on review model which calls calculate_specific_rating function of product model. The function goes like this:
def calculate_specific_rating
ratings = reviews.reload.all.pluck(:rating)
specific_rating = Hash.new(0)
ratings.each { |rating| specific_rating[rating] += 1 }
self.specific_rating = specific_rating
save
end
Right now, it returns
specific_rating => {
"2"=> 3, "4"=> 1
}
I want it to return like:
specific_rating => {
"1"=> 0, "2"=>3, "3"=>0, "4"=>1, "5"=>0
}
Also, is it okay to initialize a new hash everytime a review is saved? I want some alternative. Thanks
You can create a range from 1 until the maximum value in ratings plus 1 and start iterating through it, yielding an array where the first element is the current one, and the second element is the total of times the current element is present in ratings. After everything the result is converted to a hash:
self.specific_rating = (1..ratings.max + 1).to_h { |e| [e.to_s, ratings.count(e)] }
save
You could also do something like this -
def calculate_specific_rating
ratings = [1,2,3,4,5]
existing_ratings = reviews.group_by(&:rating).map{|k,v| [k, v.count]}.to_h
Hash[(ratings - existing_ratings.keys).map {|x| [x, 0]}].merge(existing_ratings)
end
which gives
{3=>0, 4=>0, 5=>0, 2=>3, 1=>1}

how to get the key value from the nested hash inside the array?

I have a array which is inside a hash. I want know the result of the student (pass/fail) using the following array. First I have to match them with particular standard and compare their marks with the hash pass and fails. And I want to get the key pass or fail based on their mark. How to achieve this using Ruby?
array = [
{
:standard =>1
:pass=>{:tamil=>30,:eng=>25,:math=>35},
:fail=>{:tamil=>10,:eng=>15,:maths=>20}
},
{
:standard =>2,
:pass=>{:tamil=>40,:eng=>35,:math=>45},
:fail=>{:tamil=>20,:eng=>25,:maths=>30}
}
]
#student is assumed to be defined
standard = array.select {|standard| standard[:standard] == #student.standard}
eng_pass = #student.eng_mark >= standard[:pass][:eng]
eng_fail = #student.eng_mark <= standard[:fail][:eng]
return [eng_pass, eng_fail, whatever_else_you_want]
So on and forth for various topics.
The syntax in reading values from this structure is something like:
array[0][:pass][:eng]
and accordingly you can do the comparison as usual in batch:
for i in 0..#students_array.length
num = # student's score
standard = # something like array[0][:pass][:eng]
if num > standard
# something like 'put "You passed!"'
end
end

retrieve data from database - hash

I have a table called audits which has a column 'changes' storing data in the form of hash
I would like to retrieve all entries with the following conditions:
- auditable_type = 'Expression'
- action = 'destroy'
- changes = { :EXP_SUBMISSION_FK =>'9999992642'}
I first tried the following code which returns me with nothing:
#deleted_history = Audit.find(:all, :conditions => ["auditable_type =? AND action = ? AND changes = ?",'Expression', 'destroy' , { :EXP_SUBMISSION_FK =>'9999992642'} ])
I then tried the following code which retrieves all entries in the 'audits' table with auditable_type = 'Expression' and action = 'destroy'.
I then loop through the resultset and discards all entries where EXP_SUBMISSION_FK is not equal to 9999992642. The code below returns me 5 entries/records
#deleted_history = Audit.find(:all, :conditions => ["auditable_type =? AND action = ?",'Expression', 'destroy' ])
#deleted_history.each do |test|
if test.changes['EXP_SUBMISSION_FK'] != 9999992642
#deleted_history = #deleted_history.reject { test }
end
end
I would like to know where did I go wrong with the first code example and whether there is a way of retrieving all entries with the aforementioned conditions in a much simpler way.
Thanks a lot for your help.
i'd do:
#deleted_history.select!{|hist| hist.changes['EXP_SUBMISSION_FK'] == '9999992642'}
One potential cause of failure is that you're looking for 9999992642 but you state before the value is '9999992642'
You just use something like below. I am storing element_values as a hash and i am selecting records based on the key/value pair.
scope :find_by_field_values, lambda {
|field_name, field_value|
(where("element_values like ?", "%\"#{field_name}\":\"%#{field_value}%"))
}
just try this based on your scenario.

Using a method while looping through an array in ruby

I am using ruby-aaws to return Amazon Products and I want to enter them into my DB. I have created a model Amazonproduct and I have created a method get_amazon_data to return an array with all the product information. When i define the specific element in the array ( e.g. to_a[0] ) and then use ruby-aaws item_attributes method, it returns the name I am searching for and saves it to my DB. I am trying to iterate through the array and still have the item_attributes method work. When i don't define the element, i get this error: undefined method `item_attributes' for #Array:0x7f012cae2d68
Here is the code in my controller.
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each { |name|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = #arr.item_attributes.title.to_s
}
EDIT: Code in my model to see if that helps:
class Amazonproduct < ActiveRecord::Base
def self.get_amazon_data(r)
resp = Amazon::AWS.item_search('GourmetFood', { 'Keywords' => 'Coffee Maker' })
items = resp.item_search_response.items.item
end
end
Thanks for any help/advice.
I'm not familiar with the Amazon API, but I do observe that #arr is an array. Arrays do not usually have methods like item_attributes, so you probably lost track of which object was which somewhere in the coding process. It happens ;)
Try moving that .item_attributes call onto the object that supports that method. Maybe amazonproduct.get_amazon_data(:r), before its being turned into an array with to_a, has that method?
It's not quite clear to me what your classes are doing but to use #each, you can do something like
hash = {}
[['name', 'Macbook'], ['price', 1000]].each do |sub_array|
hash[sub_array[0]] = sub_array[1]
end
which gives you a hash like
{ 'name' => 'Macbook', 'price' => 1000 }
This hash may be easier to work with
#product = Product.new
#product.name = hash[:name]
....
EDIT
Try
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each do |aws_object|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = aws_object.item_attributes.title.to_s
end
end

Resources