Ruby on Rails: Get specific substring out of string - ruby-on-rails

a little help with getting data out of a string.
Assuming I executed a sql query and now have a string(which set as hash on db):
"{\"users_associated\":{\"User:4\":6,\"User:22\":28,\"User:30\":36}}"
(Which stands for User:ID : User.display_id)
How can I get a substring the includes all users ids or all their display ids, so I'll have something like 4,22,30 or 6,22,36)?
Thanks!

It's common for data systems to return data in a serialized form, i.e. using data types that facilitate transmission of data. One of these serializable data types is String, which is how your JSON data object has been received.
The first step would be to de-serialize (or parse) this String into a Hash object using JSON.parse and tease out just the data value for key "users_associated".
your_string = "{\"users_associated\":{\"User:4\":6,\"User:22\":28,\"User:30\":36}}"
hash = JSON.parse(your_string)
data = hash["users_associated"]
#=> {"User:4":6, "User:22": 28, "User:30": 36}
Hash#keys gives you an array of a hash's keys.
Hash#values gives you an array of a hash's data values.
keys = data.keys
#=> ["User:4", "User:22", "User:30"]
values = data.values
#=> [6, 28, 36]
Array#join lets you string together the contents of an array with a defined separator, , in this case.
display_ids = keys.join(',')
#=> "6,28,36"
For the User IDs, you could Array#map every element of the values array to replace every string occurrence of "User:" with "", using String#gsub.
user_ids = values.map{|user_id| user_id.gsub("User:", "")}
#=> ["4", "22", "30"]
Then, in a similar way to display_ids, we can Array#join the contents of the user_ids array to a single string.
user_ids = user_ids.join(",")
#=> "4,22,30"

You can create two helper methods. I'm leaving return values as arrays because I assume you would need to iterate on them at some point and also converting the user id's to integers.
def extract_display_ids(json)
json['users_associated'].values
end
def extract_user_ids(some_data)
json['users_associated'].keys.map{ |key| key.split(':').last.to_i }
end
some_data = JSON.parse("{\"users_associated\":{\"User:4\":6,\"User:22\":28,\"User:30\":36}}")
extract_display_ids(some_data)
#=> [6, 28, 36]
extract_user_ids(some_data)
#=> [4, 22, 30]
If possible though, I would recommend trying to get a better data format:
{ users_associated:
[{ user_id : 4, display_id:6 }, { user_id : 4, display_id:6 }]
}

I wrote class for this. If you want, you can add it to your project and use it as follows:
require 'json'
class UserSubstringExtractor
def initialize(user_json_data)
#user_json_data = user_json_data
end
def display_ids
user_data.dig('users_associated').values
end
def user_ids
user_data.dig('users_associated').keys.map { |u| u.split(':').last.to_i }
end
private
def user_data
JSON.parse(#user_json_data)
end
end
user_json_data = '{"users_associated":{"User:4":6,"User:22":28,"User:30":36}}'
extractor = UserSubstringExtractor.new(user_json_data)
p extractor.display_ids
#=> [6, 28, 36]
p extractor.user_ids
#=> [4, 22, 30]

Related

Rails ActiveRecord join table with array of ids without n+1 request

I need to retrieve records from an array of Ids like:
User.where(id: [1,1,2])
BUT the problem is that I only get two records from this request and I want to have the [#User id:1] in double as number 1 appears twice in the array.
I could do a n+1 request but that's not very optimistic...
The steps you should follow if you want a single simple query to database:
Create a hash based on the array like:
hash = {1=>2, 2=>1, 3=>1, 4=>3} for [1, 1, 2, 3, 4, 4, 4]
Query simply:
users = User.where(id: your_array.uniq)
# Your case
users = User.where(id: [1, 1, 2].uniq) # because [1, 1, 2].uniq => [1, 2]
make your array:
the_way_i_want = []
hash.each do |key, value|
value.times do
the_way_i_want.push users.where(id: key)
end
end
set the users = the_way_i_want and return
users = the_way_i_want
crude but will save database access :-)
How about creating 1 request to DB, and then just format your data
user_ids = [1, 1, 2] # this you have
Create a hash { user_id: user_record, .... }
users = User.where(id: user_ids).map { |u| [u.id, u]}.to_h
Then create an array with all the records you want
user_ids.map do |u_id|
users[u_id]
end

What is the best way to access an element from 2d array saved as a hash value?

I have a hash, its values are 2 dimensional arrays, e.g.
hash = {
"first" => [[1,2,3],[4,5,6]],
"second" => [[7,88,9],[6,2,6]]
}
I want to access the elements to print them in xls file.
I did it in this way:
hash.each do |key, value|
value.each do |arr1|
arr1.each do |arr2|
arr2.each do |arr3|
sheet1.row(row).push arr3
end
end
end
end
Is there a better way to access each single element without using each-statement 4 times?
The desired result is to get each value from key-value pair as an array, e.g.
=> [1,2,3,4,5,6] #first loop
=> [7,88,9,6,2,6] #second loop
#and so on
hash = { "first" =>[[1, 2,3],[4,5,6]],
"second"=>[[7,88,9],[6,2,6]] }
hash.values.map(&:flatten)
#=> [[1, 2, 3, 4, 5, 6], [7, 88, 9, 6, 2, 6]]
Isn't it as simple as something like:
hash.each do |k,v|
sheet1.row(row).concat v.flatten
end

iterate over an array of hashes to create data in rails

I have an array of hashes that I am trying to seed into a database.
shoe_array = [{:department_id=>8, :follower_id=>31}, {:department_id=>9, :follower_id=>41}, {:department_id=>4, :follower_id=>49}, {:department_id=>2, :follower_id=>58}, {:department_id=>5, :follower_id=>36}, {:department_id=>9, :follower_id=>63}, {:department_id=>2, :follower_id=>52}, {:department_id=>23, :follower_id=>26}, {:department_id=>5, :follower_id=>52}, {:department_id=>6, :follower_id=>30}]
shoe_array.each do |n, k|
department_id = n,
follower_id = k,
user_id = 1
Relationship.create!(department_id: department_id,
follower_id: follower_id,
user_id: user_id)
end
I'm only getting null values for both department_id and follower_id. user_id is working.
I have tried using "#{n}" and "#{k}", to get the key values set to department and follower ids. I've also tried iterating over the array only using .each do |a| and setting department_id: a['department_id'], follower_id a['follower_id']
as seen here: iterate through array of hashes in ruby and here :How do I iterate over an array of hashes and return the values in a single string?
but I'm only still getting null values. How can I get my values into the database?
shoe_array is an array of hashes, so you should iterate over each hash, and access each key-value pair:
shoe_array.each do |hash|
department_id = hash[:department_id]
follower_id = hash[:follower_id]
user_id = 1
Relationship.create!(
department_id: department_id,
follower_id: follower_id,
user_id: user_id
)
end
According the documentation you can create records from an array of hashes:
Following should work (You can use create! as well as create)
shoe_array = [{:department_id=>8, :follower_id=>31}, {:department_id=>9, :follower_id=>41}, {:department_id=>4, :follower_id=>49}, {:department_id=>2, :follower_id=>58}, {:department_id=>5, :follower_id=>36}, {:department_id=>9, :follower_id=>63}, {:department_id=>2, :follower_id=>52}, {:department_id=>23, :follower_id=>26}, {:department_id=>5, :follower_id=>52}, {:department_id=>6, :follower_id=>30}]
Relationship.create!(shoe_array.map{|arr| arr.merge!({user_id: 1})})
Change your iteration to
shoe_array.each do |shoe|
department_id = shoe[:department_id]
follower_id = shoe[:follower_id]
An example that can use |n, k| would be either a hash or an array of arrays. If you want to go down that route, you can call values on each hash in the array (assuming that the hash is consistent, meaning department_id always comes first before follower_id)
ids = shoe_array.map(&:values) # [[8, 31], [9, 41], [4, 49], [2, 58], [5, 36], [9, 63], [2, 52], [23, 26], [5, 52], [6, 30]]
Then you can just use your old code or refactor to
ids.each do |department_id, follower_id|
Relationship.create!(
department_id: department_id,
follower_id: follower_id,
user_id: 1
)
end
Take note though that you are iterating over the array twice and will be less efficient compared to the first one.
UPDATE
Another option is use the array elements as is.
shoe_array.each do |attributes|
relationship = Relationship.new(attributes)
relationship.user_id = 1
relationship.save!
end

match key of hash and then fetch values accordingly in ruby

I have included the given code:
#classes = {1=>"USA", 3=>"France", 2=>"UK", 5=>"Europe", 7=>"Delhi", 8=>"test"}
#amaze = params[:test] #I get "1,3,7"
I get this, now please guide me how to match keys with #amaze and accordingly fetch its values from #classes i.e USA, France, Delhi.
Since #amaze is just a String, lets first convert it in Array so its easy to enumerate:
#amaze = "1,3,7"
#amaze = #amaze.split(",")
# => ["1", "3", "7"]
Now, since you have all keys extract all values:
#amaze.map { |i| #classes[i.to_i] }
# => ["USA", "France", "Delhi"]
Split #amaze by , and get an array of keys, convert them into Integer, then select only those key/value pairs which key is into this array of keys. Something like this:
#classes = {1=>"USA", 3=>"France", 2=>"UK", 5=>"Europe", 7=>"Delhi", 8=>"test"}
#amaze = "1,3,7" #I get "1,3,7"
arr = #amaze.split(',').map(&:to_i)
p #classes.select{|el| arr.include? el}
Result:
#> {1=>"USA", 3=>"France", 7=>"Delhi"}
If you want values only use .values:
p #classes.select{|el| arr.include? el}.values
Result:
#> ["USA", "France", "Delhi"]
For what(seemingly) you are asking, the below line will do it:
#amaze.split(",").each { |i| p #classes[i.to_i] }
# If #amaza = "1,3,7", above line will output:
# => "USA"
# "France"
# "UK"
This should work well for you:
#classes = {1=>"USA", 3=>"France", 2=>"UK", 5=>"Europe", 7=>"Delhi", 8=>"test"}
#amaze = params[:test].split(",").map(&:to_i)
#classes.values_at(*#amaze)
#=> ["USA", "France", "Delhi"]
Hash#values_at accepts an indefinite number of keys and returns their values as an array. The * (splat) operator explodes the array so this call actually becomes #classes.values_at(1,3,7) Docs
Might also want to add a compact to the end in the event a key does not exist. e.g
#amaze = params[:test].split(",").map(&:to_i) # Asssume this returns [1,3,7,9]
#classes.values_at(*#amaze)
#=> ["USA", "France", "Delhi",nil]
#classes.values_at(*#amaze).compact
#=> ["USA", "France", "Delhi"]
I think a clearer understanding of hashes would help you out here.
A Hash is a data structure that is a list of key-value pairs. For example, the following is a Hash object of key-value pairs (your example):
#classes = {1=>"USA", 3=>"France", 2=>"UK", 5=>"Europe", 7=>"Delhi", 8=>"test"}
If you want to extract a value from #classes, you need to pass the key of the value you want. If we wanted "USA" we would pass the key of 1 to #classes. If we wanted "France", we would pass it the key of 3:
#classes[1] would return "USA" and #classes[3] would return "France".
It's not clear what data structure #amaze is according to your question, but let's say it's the string "1, 3, 7" which we can split to create an array [1, 3, 7].
You could iterate over the array to get each of the values from #classes:
#amaze.split(",").map(&:to_i).each do |key|
puts #classes[key]
end
That would print out each of the corresponding values to keys in #classes.

Ruby Array Having Hash Pairs?

I have the ruby array as following :
array = [{"id"=>8, "book_id"=>14238}, {"id"=>5, "book_id"=>14238}, {"id"=>7, "book_id"=>10743}, {"id"=>9, "book_id"=>10743}]
I want a new array combining the result of ids having same book_id.
Expected Result:
array = [{"book_id"=>14238, "id"=>[8,5]}, {"book_id"=>10743, "id"=>[7,9]}]
I can't say that this is easy to understand, but it is concise:
array.group_by {|item| item["book_id"] }.map do |k, v|
{ "book_id" => k, "id" => v.map {|item| item["id"] } }
end
=> [{"book_id"=>14238, "id"=>[8, 5]}, {"book_id"=>10743, "id"=>[7, 9]}]
The first transformation done by group_by rearranges your array so that items with the same book_id are grouped together:
array.group_by {|item| item["book_id"] }
=> {14238=>[{"id"=>8, "book_id"=>14238}, {"id"=>5, "book_id"=>14238}], 10743=>[{"id"=>7, "book_id"=>10743}, {"id"=>9, "book_id"=>10743}]}
The second transformation (map) reformats the hash produced by the group_by into a list of hashes, and the second map collects the id's into a list.
You can also do this using the form of Hash#update (a.k.a. merge!) that employs a block to resolve the values of keys that are contained in both of the hashes being merged.
Code
def aggregate(arr)
arr.each_with_object({}) do |g,h|
f = { g["book_id"]=>{ "id"=>[g["id"]], "book_id"=>g["book_id"] } }
h.update(f) do |_,ov,nv|
ov["id"] << nv["id"].first
ov
end
end.values
end
Example
arr = [{"id"=>8, "book_id"=>14238}, {"id"=>5, "book_id"=>14238},
{"id"=>7, "book_id"=>10743}, {"id"=>9, "book_id"=>10743},
{"id"=>6, "book_id"=>10511}]
aggregate(arr)
#=> [{"id"=>[8, 5], "book_id"=>14238},
# {"id"=>[7, 9], "book_id"=>10743},
# {"id"=>[6], "book_id"=>10511}]
Alternative output
Depending on your requirements, you might consider building a single hash instead of another array of hashes:
def aggregate(arr)
arr.each_with_object({}) { |g,h|
h.update({ g["book_id"]=>[g["id"]] }) { |_,ov,nv| ov+nv } }
end
aggregate(arr)
#=> {14238=>[8, 5], 10743=>[7, 9], 10511=>[6]}
I'd use a hash for the output for easier lookups and/or reuse:
array = [{"id"=>8, "book_id"=>14238}, {"id"=>5, "book_id"=>14238}, {"id"=>7, "book_id"=>10743}, {"id"=>9, "book_id"=>10743}]
hash = array.group_by{ |h| h['book_id'] }.map{ |k, v| [k, v.flat_map{ |h| h['id'] }]}.to_h
# => {14238=>[8, 5], 10743=>[7, 9]}
The keys are the book_id values, and the associated array contains the id values.
The expected result of
array = [{"book_id"=>14238, "id"=>[8,5]}, {"book_id"=>10743, "id"=>[7,9]}]
isn't a good structure if you're going to do any sort of lookups in it. Imagine having hundreds or thousands of elements and needing to find "book_id" == 10743 in the array, especially if it's not a sorted list; The array would have to be walked until the desired entry was found. That is a slow process.
Instead, simplify the structure to a simple hash, allowing you to easily locate a value using a simple Hash lookup:
hash[10743]
The lookup will never slow down.
If the resulting data is to be iterated in order by sorting, use
sorted_keys = hash.keys.sort
and
hash.values_at(*sorted_keys)
to extract the values in the sorted order. Or iterate over the hash if the key/values need to be extracted, perhaps for insertion into a database.

Resources