Compare hash with column - Rails - ruby-on-rails

I'm getting a hash from an API call that returns values like the following:
[{"name"=>"Abby Allen", "id"=>"123"}, {"name"=>"Barry Burner", "id"=>"234"}, {"name"=>"Cat Catrelli", "id"=>"345"}, {"name"=>"Darrell Dogooder", "id"=>"456"}, {"name"=>"Eva Ewing", "id"=>"567"}]
I'd like to compare the id's from this hash to the "apiid" column that I current have in my database (User model), and return all IDs that the hash and "apiid" column have in common.
i.e. my apiid column in my User model looks like this
apiid
001
123
125
333
345
I was trying it this way, but any suggestions can't get it to work. Eventually, the apiid column will be long, so I'm looking for the most efficient way as well.
User.find_each(:select => "apiid") do |user|
#friendscommon = #friends.select{|key, hash| hash["id"] == user }
end
where #friends is the hash above.
Any suggestions would be appreciated. Thank you!

You want to extract an array of all the ids in the array of hashes:
apiids = hash.map { |user| user["id"] }
This will cause apiids to be an array of the ids, ie:
apiids = ["123","234","345","456","567"]
And pass it into a query:
User.select(:apiid).where(:apiid => apiids)
Here, the where clause effectively sees:
where(:apiid => ["123","234","345","456","567"])
which translates to:
SELECT apiid FROM users WHERE (users.apiid IN ("123","234","345","456","567"))

Related

Rails 4 Using Ruby Enumerable to query arrays using arrays

I have a model that stores an array in one of the table columns called 'attributes'. So 3 separate records might look like this:
Record 1
MyModel.attributes = {Red, Furry, Stinky}
Record 2
MyModel.attributes = {Red}
Record 3
MyModel.attributes = nil
Record 4
MyModel.attributes = {Blue, Furry, Sweet}
I'd like to query this array for any of another array, including nil. The results should return any records that have any of the attributes in the query array and any records where the attributes column is nil.
query_array = [Blue, Furry]
The answer to this query should provide Record 1, Record 3 and Record 4 -- again, it's not looking for ALL the
currently, I can do this if I just query
MyModel.all.select {|m| m.attributes["Furry"] or m.attributes["Blue"] }
But I want to be able to create the array dynamically and not handcode the m.attributes["attribute"]. I can't quite figure out how to do this without requiring all of the array items, I just want ANY of the array items and records with no attributes.
You probably should not call the column attributes as this name is already used for the attributes hash accessors in rails models. For the examples below i renamed this to tags
A simple solution would be to check for nil (always include those records) and check if the intersection of tags has any tags in it:
model_1.tags # => ['red', 'furry', 'stinky']
model_2.tags # => ['red']
model_3.tags # => nil
model_4.tags # => ['blue', 'furry', 'stinky']
search_tags = ['red', 'blue']
MyModel.all.select do |model|
model.tags.nil? || (model.tags & search_tags).any?
end
You could also write it as a nested loop:
search_tags = ['red', 'blue']
MyModel.all.select do |model|
model.tags.nil? || model.tags.any? { |tag| search_tags.include?(tag) }
end
This is all done in memory, in ruby itself. If you have 100_000 or 1_000_000 records, then all of them are fetched from DB, instantiated and then filtered.
So depending on your exact requirements and what DB you are using you could find an easier/more performant solution. Some ideas:
Store the tags in a separate table
Store the tags as a comma separated string and use a 'like' query
Use postgres JSON datatype and the query features postgres provides
You can try this
query_array = [Furry, Blue]
query_string = query_array.map{|query| "m.attributes[#{query}]" }.join(" || ")
#=> "m.attributes[Furry] || m.attributes[Blue]"
MyModel.all.select {|m| eval(query_string) }
Now, all you have to do is add more items into the query_array.

Activerecord query with array in where clause

I have a location table in my rails app which has four columns :-
| Id | Longitude | Latitude | user_id
Now I have an array containing the list of user_ids. How can I write an active record query to select the last row of each user id. For example, if I have three user_ids in my array [1,2,3] I want the query to return just the last row corresponding to each user_id (lets assume entries for each user_id is present in the table) from the table.
So, far I am able to get all the rows corresponding to all the user_ids using this following query:
#ids = [1,2,3]
#locations = Location.where(user_id: ids)
How can I modify this activerecord query so that it returns only the last row corresponding to each user_id
Assuming you have a User model that has many locations, you could start from the user model and use the association to get to your last location for each user.
User.where(:id => #ids).includes(:locations).collect { |u| u.locations.last }
User.where(:id => #ids) returns your collection of user objects.
includes(:locations) eager loads the associated locations, so we don't run into an n+1 problem.
collect { |u| u.locations.last } maps the last associated location into an array
You can also try this:
#ids = [1,2,3]
#locations = #ids.map {|id| [Location.where(user_id: id).last.location,User.find(id).name]}
#This would give you something like this: [["US",XYZ],["IND","ABC"]]

How to update collection data for collection of ids?

I am getting collection of ids [1,2,3,4] in the params and I make a call to an API that will return the array for the corresponding ids. E.g. ["Apple","Orange","Mango"]. How can I update this in my database for the corresponding ids?
For example: the ids which are mentioned above are from my user table. ids = [1,2,3,4], which are primary keys in my user table.
From the API response I got an array of fruit_names for the correlated user_ids. E.g.: ids = [1,2,3,4] and fruit_names = ["a","b","c","d"], where the fruit_name column exists in my user table. How do I update fruit_name from the API response correlated ids in my user table?
You can use each_with_index in combination with update for this:
ids.each_with_index do |id, index|
User.update(id, :fruit_name, fruit_names[index])
end
The above code assumes:
ids = [1,2,3,4]
fruit_names = ["a","b","c","d"]
and that the indexes of those arrays match.
Note that this will execute a query for each item in your ids array. If your ids array is very big this is not going to perform well.
Hash[ids.zip fruit_names].each do |id, fruit|
User.update_all({:fruit_name => fruit}, {:id => id})
end
OR
User.where(:id => ids).each do |usr|
usr.update_attribute(:fruit_name, fruit_names[ids.index(usr.id)])
end

Rails returns a nil, when searching with find_by

I'm beginning to learn RoR, but i've a problem which i don't understand. With Product.find :all returns all the records from DB. But if i want to find_by_gender(1) (or even 2) it returns a nil, i'm certain that the db contains products with a gender
My code controller:
gender = params[:gender].to_i
#search_results = Product.find_by_gender(gender)
this returns a nill,
What am i doing wrong?
Greetings!
find_by_... returns either first record or nil if none found, find_all_by_... returns all records that match (or empty array if none). In your case nil means no records found with gender = 1.
Verify your data first!
Look at some sample records:
Do something like:
Product.all(:limit => 5).each {|product| product.id.to_s + product.gender}
or go into sql
sql> select id, gender from products where id < 6;
If you are to verify what the gender values are you can then create named scopes in your model for those conditions, e.g. (rails3)
(Product Model - app/models/product.rb)
scope :male where(:gender) = male_value # i.e. 1 or 'M' or 'Male' or whatever
scope :female where(:gender) = female_value # i.e. '2' or 'F' or whatever
Which will then you let write Products.male or Products.female !
Final note - should gender be in your users table? , or is this for male / female specific products?
in rails console execute
Product.pluck(:gender)
And u will know that values does it have in AR(i think true and false), so u have to use query Product.find_by_gender(true)

grouping comments by parent object, ordering parent object by oldest comment

I have objects that have comments. As part of a periodic email summary, I want to determine comments for a period, and present the objects in the order of oldest commented object first.
Data:
object_id comment_id
30 40
40 42
32 41
30 43
32 44
Output:
Object #30
comment 40
comment 43
Object #40
comment 42
Object #32
comment 41
comment 44
I am using this code to get the data to an intermediate array - I tried to get it all in one swoop using .group_by(&:commentable_id) but the data didn't come out in correct order.
comments = account.comments.all(
:conditions => ["comments.created_at > ?", 8.hours.ago],
:order => "comments.created_at asc" ).map { |c| [c.commentable_id,c.id] }
=> [ [30,40], [40,42], [32,41], [30,43], [32,44] ]
If I can get that data to transform into the following form, I could just iterate over the array to build the email content...
[ [30,[40,43]], [40,[42]], [32,[41,44]] ]
But I wonder if I'm making this harder than I need to... Any advice?
(I'm using Rails 2.3 and Ruby ree-1.8.7)
You can use a group with an array aggregate to get to the array form that you're looking for.
Array aggregates are massively db dependent. MySQL's is GROUP_CONCAT. Postgres' is ARRAY_AGG. Sqlite doesn't have one out of the box, but I know you can define custom aggregate functions, so it's not impossible.
Haven't actually tried running this code, but here's something that should point you in the right direction:
result = Object.all(
:select => 'objects.id, GROUP_CONCAT(comment_id) AS comment_array',
:group => 'comments.id'
).map { |c| [c.id, c.comment_array] }
I used the naming from the first example, so you'll need to change 'object' to whatever your table is called. Hope it makes sense. Rails probably doesn't have inbuilt support for parsing an array, so it will probably return a string for comment_array, and you might have to parse it.
Having all the comments for a single object in a single block/element will definitely make life easier while doing any operation on them. However, I won't go as far as turning them into an array of array of arrays because it is already an array of arrays. I would prefer creating a hash like so:
comments_array = [ [30,40], [32,41], [40,42], [30,43], [32,44] ]
obj_with_comments = {}
comments_array.each do |x|
obj_with_comments[x.first] ||= []
obj_with_comments[x.first] << x.last
end
obj_with_comments #=> {40=>[42], 30=>[40, 43], 32=>[41, 44]}
But this presents another problem which is, hashes are not ordered, so you loose your ordering in some random fashion if you just iterate over the hash. However, you can create an array of objects then iterate over the hash like so:
objects = comments_array.collect{|x| x.first}.uniq
objects #=> [30, 32, 40]
# now get the hash value for each object key in order
objects.each { |obj| puts obj_with_comments[obj].inspect }
Hope that makes sense.
Try this:
comments = account.comments.all(
:conditions => ["comments.created_at > ?", 8.hours.ago],
:order => "comments.commentable_id ASC, comments.id ASC"
).map { |c| [c.commentable_id,c.id] }
This will return the following result set:
=> [ [30,40], [30,43], [32,41], [32,44], [40,42] ]
In the query above I am using id for sorting instead of create_at. If you are using MySQL and if the id's are auto generated this logic will work as the id of a new object will be higher than the id of an older object. If you don't allow editing of comments then this logic will work.
If you want to explicitly sort by the dates then use the following syntax:
comments = account.comments.all(
:joins => "accounts AS accounts ON comments.commentable_type = 'Account' AND
comments.commentable_id = accounts.id",
:conditions => ["comments.created_at > ?", 8.hours.ago],
:order => "accounts.id ASC, comments.id ASC"
).map { |c| [c.commentable_id,c.id] }

Resources