Saving attribute from array of objects in Rails - ruby-on-rails

I'm passing as array of JSON object as a param in Rails through an AJAX post/delete, like
[{'id': 3, 'status': true}, {'id': 1, 'status': true}]
How do I loop through the params[:list] to get each id and status value?

Do this:
params[:list].each do |hash|
hash['id'] # => 3
hash['status'] # => true
end

Try like follows:
params[ :list ][ 0 ][ :id ] # => 3, ...
params[ :list ].each {| v | v[ :id ] } # => 3, 1
In case if your hash is like ["0", {"id"=>"9", "status"=>"true"}]:
# a = ["0", {"id"=>"9", "status"=>"true"}]
h = Hash[[a]]
# => {"0"=>{"id"=>"9", "status"=>"true"}}
and access to it will be:
h['0'][ :id ] # => '9'

There were two steps to this, plus what #Agis suggested. (Thanks #Agis)
I had to first stringify the JSON:
'p': JSON.stringify(p),
So that it does not create that wierd array. There was another stackoverflow answer using this method to generate a correct data JSON object, but it was encapsulating the whole data... I need to have this on just p and not affect 'stringifying' the security token.
Understood this from here: Rails not decoding JSON from jQuery correctly (array becoming a hash with integer keys)
Next, I had to decode just that param, using
ActiveSupport::JSON.decode(params[:p])
a clue from here: How do I parse JSON with Ruby on Rails?
Lastly, can I then use the loop and access item['id']

Here's a loop:
params[:list].each do |item|
item.id
item.satus
end
If you want to create an array of id's or something:
list_ids = params[:list].map(&:id)

Related

Ruby change hash to single layer with square brackets

I've got a hash and I've found that with net/http posting I have to convert it into a flat format.
Example
invoice = { :no => "100", :date => "08/08/2022", :client => {:name => "Foo" } }
Would become
params = { "invoice[no]" => "100", "invoice[date]" => "08/08/2022", "invoice[client][name]" => "Foo" }
Is there a way to do this automatically? I've tried to_param & to_query, flatten and encode_www_form but they don't convert it to this required format.
The post action I'm doing is to a Ruby On Rails backend which I use Devise Tokens to authorise.
res = Net::HTTP.post_form(uri, params)
You need CGI.parse method. It parses an HTTP query string into a hash of key => value pairs
CGI.parse({ invoice: invoice }.to_query)
# => {"invoice[client][name]"=>["Foo"], "invoice[date]"=>["08/08/2022"], "invoice[no]"=>["100"]
Don't care about single-element arrays as values. It will works well
params = CGI.parse({ invoice: invoice }.to_query)
res = Net::HTTP.post_form(uri, params)
I think this snippet should do the job:
invoice = { :no => "100", :date => "08/08/2022", :client => {:name => "Foo" } }
CGI.unescape({invoice:}.to_query)
.split('&')
.map{ |p| p.split('=') }
.to_h
{"invoice[client][name]"=>"Foo", "invoice[date]"=>"08/08/2022", "invoice[no]"=>"100"}
First of all, we let ActiveRecord generate the query-like structure from a hash using the method to_query. We need to unescape the query string afterward since we don't want to have URL-encoded output there. After that we split the string by parameter using split('&') and every parameter into key-value using split('='). Finally, we convert the output back into a hash.

Apply deep_symbolize_keys! to array of hashes

deep_symbolize_keys! converts string keys to symbol keys. This works for hashes and all sub-hashes. However, I have a data like this:
arr = [
{'name': 'pratha', 'email': 'p#g.com', 'sub': { 'id': 1 } },
{'name': 'john', 'email': 'c#d.com', 'sub': { 'id': 2 } }
]
arr.deep_symbolize_keys! # this is not working for array of hashes.
In this case, hashes are in an array. So how can i symbolize all at once?
Using Ruby 2.6.3
I also read somewhere that this is deprecated (probably on one of the Rails forum). Is that true? If so, what is the best way to convert keys to symbols in my case?
Currently using this:
def process(emails)
blacklist = ["a", "john", "c"]
e = emails.map do |hash|
blacklist.include?(hash['name']) ? nil : hash.deep_symbolize_keys!
end
e
end
Do you need a copy or an in-place transformation? In-place you can use arr.each(&:deep_symbolize_keys!). For a copy you should use arr.map(&:deep_symbolize_keys). Remember that map does not mutate but returns a new array.
The implementation already handles nested arrays, it just doesn't define the method on Array. So, nest it in a temporary hash and symbolize that. This works for arbitrary types:
[1] pry(main)> def deep_symbolize_keys(object) = {object:}.deep_symbolize_keys[:object];
[2] pry(main)> deep_symbolize_keys([{"a" => 1}, {"b" => {"c" => 2}}])
=> [{:a=>1}, {:b=>{:c=>2}}]
Also, be careful with your key syntax. In your example, your keys are already symbols - they're just quoted symbols:
[3] pry(main)> {a: 1}.keys.first.class
=> Symbol
[4] pry(main)> {'a': 1}.keys.first.class
=> Symbol
[5] pry(main)> {'a' => 1}.keys.first.class
=> String
The syntax is necessary to handle cases like {'a-b': 1}[:'a-b'], but it's very often misleading since they look so much like string keys. I recommend avoiding it entirely unless absolutely necessary - stick to {a: 1} for symbol keys and {'a' => 1} for string keys.

Passing identically-named params to a Rspec POST request to create an array

I have an API call where I need to POST an array of IDs. I know that if I pass several params called ids[] to my request, they'll appear as an array in the params hash in the controller:
# POST /api/events
# params:
# ids[] = 1
# ids[] = 2
# ids[] = 3
# Then in Api::EventsController:
puts params # => { ids: [ "1", "2", "3"] }
But how can I test this? I can't use the same parameter name twice in my RSpec test:
post "/api/events", :"ids[]" => 1, :"ids[]" => 2, :"ids[]" => 3
Because what that really means is:
post "/api/events", {:"ids[]" => 1, :"ids[]" => 2, :"ids[]" => 3}
... and hashes can't have the same key twice, so the second argument gets reduced to just {:"ids[]" => 3}.
And something like :"ids[]" => "1,2,3" doesn't work, it just results in ids: ["1,2,3"].
What should I pass to the post method to get an array in the params hash of my controller?
*Facepalm*
I don't know why this didn't occur to me, but you can just pass an array as one of the hash values:
post "/api/events", :ids => [1, 2, 3]
Guess I was taking the URL syntax a little bit too literally.
Hopefully this answer will save a future Googler 30 seconds.

Code snippet for comparing hashes with similar keys

I am writing integration tests for rails and I want to compare the object created with the JSON object sent. The object returned is not exactly the same as the one sent, (i.e.) it has keys that the object sent doesn't have because I am using active model serializers to pull associations in the returned object. Basically, I just want to compare all the same keys between both objects to see if its the same. Let me know if there is a clean efficient code snippet that does this for me!
TL;DR
"Clever" test code is rarely useful. Each test should be as simple as possible, and it should be testing the behavior of some object rather than its composition. There are always ways to be clever, though.
Using Array Intersection
One unreadably-clever way to do this is to use Array#& to find the intersection of the keys, and then look for equality between the values. This will work on a relatively flat hash. For example:
hash1 = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4"}
hash2 = {:key1=>"value1", :key2=>"value2", :key5=>"value5"}
Array(hash1.keys & hash2.keys).map { |k| hash1[k] == hash2[k] }.uniq
#=> [true]
If you're using RSpec to test, you could say something like:
it 'has some matching key/value pairs' do
# ... populate hash1
# ... populate hash2
Array(hash1.keys & hash2.keys).
map { |k| hash1[k] == hash2[k] }.uniq.should == [true]
end
Of course, if the expectation is false, then you won't really know why, or which key/value pair was wrong. This is just one of the many reasons that you should always use fixed inputs and outputs for testing, rather than trying to do dynamic comparisons.
You could use Hash#slice, which is an Active Support core extension.
For example, if the keys you want to check are :a, :b, :c, but the result contains :a, :b, :c, :d, slice will reduce the result to just contain the keys you care about:
expected = { :a => 1, :b => 2, :c => 3 }
result = { :a => 1, :b => 2, :c => 3, :d => 4 }
result.slice(:a, :b, :c) == expected
# => true
If you get a NoMethodError: undefined method 'slice' exception, you need to require active_support/core_ext/hash/slice
First, find the keys that both hashes contain, and then compare the value for those keys:
hash1 = {:key1 => "value1", :key2 => "value2", :key3 => "value3", :key4 => "value4"}
hash2 = {:key1 => "value1", :key2 => "value2", :key5 => "value5"}
hash1_keys = hash1.keys
hash2_keys = hash2.keys
comparable_keys = hash1_keys.select{|key| hash2_keys.include?(key)}
comparable_keys.each do |key|
hash1[key].should == hash2[key]
end

Getting ruby hash values by an array of keys

What I'm aiming to do is to create an object which is initialized with a hash and then query this object in order to get values from that hash.
To make things clearer here's a rough example of what I mean:
class HashHolder
def initialize(hash)
#hash = hash
end
def get_value(*args)
# What are my possibilities here?
end
end
holder = HashHolder.new({:a => { :b => { :c => "value" } } } )
holder.get_value(:a, :b, :c) # should return "value"
I know I can perform iteration on the arguments list as in:
def get_value(*args)
value = #hash
args.each do |k|
value = value[k]
end
return value
end
But if I plan to use this method a lot this is going to degrade my performance dramatically when all I want to do is to access a hash value.
Any suggestions on that?
To update the answer since it's been a while since it was asked.
(tested in ruby 2.3.1)
You have a hash like this:
my_hash = {:a => { :b => { :c => "value" } } }
The question asked:
my_hash.get_value(:a, :b, :c) # should return "value"
Answer: Use 'dig' instead of get_value, like so:
my_hash.dig(:a,:b,:c) # returns "value"
Since the title of the question is misleading (it should be something like: how to get a value inside a nested hash with an array of keys), here is an answer to the question actually asked:
Getting ruby hash values by an array of keys
Preparation:
my_hash = {:a => 1, :b => 3, :d => 6}
my_array = [:a,:d]
Answer:
my_hash.values_at(*my_array) #returns [1,6]
def get_value(*args)
args.inject(#hash, &:fetch)
end
In case you want to avoid iteration at lookup (which I do not feel necessary), then you need to flatten the hash to be stored:
class HashHolder
def initialize(hash)
while hash.values.any?{|v| v.kind_of?(Hash)}
hash.to_a.each{|k, v| if v.kind_of?(Hash); hash.delete(k).each{|kk, vv| hash[[*k, kk]] = vv} end}
end
#hash = hash
end
def get_value(*args)
#hash[args]
end
end
If you know the structure of the hash is always in that format you could just do:
holder[:a][:b][:c]
... returns "value".

Resources