Rails group by id uses a string for hash key - ruby-on-rails

My code looks like this:
hash = MyModel.count(:group => 'id', :conditions => 'bla = "bla"')
The returned Hash has keys that are strings. I want them to be ints. I know it would be possible to convert the Hash manually using something like a map construct.
Edit:
Thanks for the responses. Have realised it was a json conversion process that was turning the ids into Strings and rails does in fact use the Fixnum as one might expect.

hash = MyModel.count(group: 'id', conditions: 'bla = "bla"')
should have Fixnum keys by default since id is an instance of Fixnum.
What happens is that ActiveRecord always fetch result as strings and then Rails takes care of converting them to other datatypes according to the type of the database column (we say that they are typecast).
So it's maybe a Rails bug or the 'id' column is not set as integer(which would be surprising).
If you can't fix it, convert them manually:
hash.each_with_object({}) do |(key, value), hash|
hash[key.to_i] = value
end

When I use your code I get integer keys (rails 3.07), what's the column type of id?
If you want to do it manually:
new_hash = hash.inject({}){|h,a| h[a.first.to_i] = a.last; h}

new_hash = Hash[hash.map { |k, v| [k.to_i, v] }

Related

Writing custom method for a hash on Ruby? on rails

i'm trying (and actually succeded, but i don't understand how it works) to write a custom method for a hash in my model (I'm working on Ruby on Rails 6).
My hash looks like this
my_hash = {
[['name_1', 'slug_1']=>value_1],
[['name_2', 'slug_2']=>value_2],
[['name_1', 'slug_1']=>value_3],
[['name_2', 'slug_2']=>value_4]
}
So basically a hash of arrays. You notice that the 'keys' are arrays that repeat themselves many times, but with different values. What i want to achieve is to write a custom method that "joins" all the keys in only one key, which will have an array of values assigned, so basically i should be able to get:
my_hash = {
['name_1', 'slug_1']=>"values": [value_1, value_3],
['name_2', 'slug_2']=>"values": [value_2, value_4]
}
For that, I have this piece of code, which i use many times:
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
Since I use this many times, i wanted to write a custom method, so i did:
def format_hash_data my_hash
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end
And used it like: my_hash = format_hash_data(my_hash) with no success(it threw an error saying that 'format_hash_data' was not a valid method for the class).
So I fiddled around and added 'self' to the name of the method, leaving:
def self.format_hash_data my_hash
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end
Which, to my surprise, worked flawlessly when using my_hash = format_hash_data(my_hash)
I don't really understand why adding 'self' makes my code works, maybe anyone can shed some light? I tried using things like send() or instance_eval first, to just send the piece of code to the actual hash as a method (something like my_hash.instance_eval(my_method)) but I couldn't get it working.
I'm sorry about the long explanation, I hope i was clear enough so any of you who had this same dilemma can understand. Thanks in advance.
Prepending self. to the method name makes it a class method instead of an instance method. If you are not sure of the difference, you should look it up as it is fundamental to properly defining and using classes and methods.
As a class method, you would use it as:
my_hash = MyHash.format_hash_data(my_hash)
Or if you're in scope of the class, simply my_hash = format_hash_data(my_hash), which is why it worked in your case with the self. prepended (class method definition).
If you want to define it as an instance method (a method that is defined for the instance), you would use it like so:
my_hash = my_hash.format_hash_data
And the definition would use the implicit self of the instance:
def format_hash_data
self.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end

How to pass an array of arrays in GET API in Ruby on Rails

I am using a GET API, currently passing an array as a string:
def fetch_details ids
url = "#{url}/api/v1/get-info?ids=#{ids.join(',')}"
response = Net::HTTP.get_response(URI.parse(URI.encode(url)))
if response.code.to_i == 200
return Oj.load(response.body)
else
return {}
end
end
On the server-side I am extracting id from this method:
def self.get_details(ids)
ids = ids.split(",").map {|x| x.gsub( " ", "")}
end
For each id, I want to send an array of UUIDs:
ids = [100,21,301]
uuids= {["abc","bca"],["Xyz"],["pqr","345"]}
Something like this
hash=[
100=>[abc,bca],
21=>[xyz],
301=>[pqr,345]
]
The endpoint uses the id and corresponding UUIDs to join two tables in database query so I should be able to extract the id and corresponding UUID at the end.
How do I pass both these values?
To pass an array in the parameters in Rails/Rack you need to add brackets to the name and repeat the parameter:
/api/v1/get-info?ids[]=1&ids[]=2&ids[]=3
You can use Hash#to_query from ActiveSupport to generate the query string:
irb(main):001:0> { ids: [1,2,3] }.to_query
=> "ids%5B%5D=1&ids%5B%5D=2&ids%5B%5D=3"
As pointed out by #3limin4t0r you should only use this for one-dimensional arrays of simple values like strings and numbers.
To pass a hash you use brackets but with keys in the brackets:
/api/v1/get-info?foo[bar]=1&foo[baz]=2
Again you can generate the query string with #to_query:
irb(main):002:0> { foo: { bar: 1, baz: 2 } }.to_query
=> "foo%5Bbar%5D=1&foo%5Bbaz%5D=2"
The keys can actually be numbers as well and that should be used to pass complex structures like multidimensional arrays or an array of hashes.

Pluck doesn't return a bother records they have the same name

I have a pluck that is turned into a hash and stored in a variable
#keys_values_hash = Hash[CategoryItemValue.where(category_item_id: #category_item.id).pluck(:key, :value)]
If 2 records have the same :key name then only the most recent record is used and they aren't both added to the hash. But if they have the same value and different keys both are added to the hash.
This also occurs if I swap :key and :value around (.pluck(:value, :key)). If they have now the same value it only uses the most recent one and stores that in the hash. But having the same key is now fine.
I'm not sure of this is being caused by pluck or from being sorted in a hash. I'm leaning towards pluck being the culprit.
What is causing this and how can I stop it from happening. I don't want data being skipped if it has the same key name as another record.
I'm not sure why you need convert pluck result into a Hash, because it was an Array original.
Like you have three CategoryItemValue like below:
id, key, value
1, foo, bar1
2, foo, bar2
3, baz, bar3
when you pluck them directly, you will get a array like:
[ ['foo', 'bar1'], ['foo', 'bar2'], ['baz', 'bar3'] ]
but when you convert it into a hash, you will get:
{'foo' => 'bar2', 'baz' => 'bar3' }
because new hash value will override the old one if key ( foo in the example above) exists.
Or you could try Enumerable#group_by method:
CategoryItemValue.where(...).pluck(:key, :value).group_by { |arr| arr[0] }

How to require an Array in Rails when using strong parameters?

Is there a way to require an array when using strong parameters in Rails 4?
> params = ActionController::Parameters.new(contacts: [])
=> {"contacts"=>[]}
> params.require(:contacts)
ActionController::ParameterMissing: param not found: contacts
As Steve Wilhelm noted, it works if the array is non-empty. It only fails on your example because the contacts array is empty. But that's usually the desired behavior.
If you don't care what's in the array, just use permit.
That said, I'd imagine the most common case is that you want an array of hashes with known keys. I would do that this way:
# Returns an array of contacts after checking the params shape
# Use instead of params[:contacts]
def contacts_params
params.permit(contacts: %i(id name phone address))
params.require(:contacts)
end
It appears you can have arrays of Scalars, This works
> params = ActionController::Parameters.new(contacts: [nil])
=> {"contacts"=>[nil]}
> params.require(:contacts)
=> [nil]
> params = ActionController::Parameters.new(contacts: [1])
=> {"contacts"=>[1]}
> params.require(:contacts)
=> [1]
Here is the description from documentation
The permitted scalar types are String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, DateTime, StringIO, IO, ActionDispatch::Http::UploadedFile and Rack::Test::UploadedFile.
To declare that the value in params must be an array of permitted scalar values map the key to an empty array:
params.permit(id: [])
Have you tried this
params.permit(contacts: []).require(:contacts)

My hashes are stacking unordered..What's a loop that can select them by their Hash ID?

My Hashes are appearing like this:
{"6"=>{":amount_paid"=>"100.00", ":date_paid"=>"4/22/2009"},
"0"=>{":amount_paid"=>"100.00", ":date_paid"=>"2/27/2008"},
"1"=>{":amount_paid"=>"80.00", ":date_paid"=>"3/27/2008"},
"2"=>{":amount_paid"=>"100.00", ":date_paid"=>"5/8/2008"},
"3"=>{":amount_paid"=>"100.00", ":date_paid"=>"6/20/2008"},
"4"=>{":amount_paid"=>"100.00", ":date_paid"=>"9/22/2008"},
"5"=>{":amount_paid"=>"100.00", ":date_paid"=>"2/20/2009"}}
The order matters to me when I loop through it with this:
params[:payments].each_with_index do |item, idx|
In this way I can add the dates by which ever date came before them.
Is there a loop that could find the sequence of "0".."6" and remain close to the same syntax?
The only other alternative I can think of is to ensure that those params get stacked in order. They come from a form like this :
= text_field_tag "payments[0][:date_paid]"
= text_field_tag "payments[0][:amount_paid]"
= text_field_tag "payments[1][:date_paid]"
= text_field_tag "payments[1][:amount_paid]"
= submit_tag 'punch it chewy!'
Hashes are unordered in Ruby 1.8, and ordered by insertion in Ruby 1.9. You can sort your hash by the key by using Enumerable#sort as seen in this thread. What you get out isn't a Hash but an array of arrays, with the first element as the keys and the second as the values. You will need to unpack these to get what you want similar to the each_with_index.
params[:payments].sort { |a, b| a[0].to_i <=> b[0].to_i }.each do |x|
item = x[1]
index = x[0]
.....
end
This has a similar syntax:
(0..6).each do |idx| item=params[:payments][idx]
# ...
end
Hash apparently keeps keys in the order they are inserted ( http://www.ruby-doc.org/core/classes/Hash.html ), so you can re-create a sorted hash this way:
Hash[params[:payments].sort]
(Apparently since Ruby 1.9.2; maybe not in all implementations)
Hashes are unordered. There is a gem called facets which has a dictionary object that is ordered.
You could also convert the hash to an array and then sort the array.
thing = {"1" => {:paid => 100, :date => '1/1/2011'}, "2" => {:paid => 100, :date => '1/12/2011'}}
thing.to_a.sort
thing.inspect
returns: [["1", {:date=>"1/1/1900", :paid=>100}], ["2", {:date=>"1/1/1900", :paid=>100}]]
You can then loop through the array in the correct order.
sorted_payments = params[:payments].keys.sort.map {|k| params[:payments][k]}
returns an array of hashes ordered by the value of the keys, which you can then enumerate with .each. This is more generalized than doing (0..6), which might (or might not) be useful.
(0..6).each do |idx|
item = params[:payments][idx]
end

Resources