Pass array as get parameters in RoR using HTTParty - ruby-on-rails

I have a rails api endpoint that takes an array as one of it's parameters. In rspec I have the following test
it 'should get a list of cars' do
car_ids = [1,2]
get :index, {car_ids: car_ids, format: :json}
end
and in the controller I have verified that
params[:car_ids] == [1,2]
However, when I use HTTParty to hit this endpoint, the ids in the arrays are Strings, not Integers.
prams[:car_ids] == ["1", "2"]
Here is my HTTParty call
HTTParty.get('api_endpoint', query: {car_ids: [1,2]})
I have tried a bunch of variations of this request, such as adding {format: :json}, and setting the content-type and accept headers but to no avail.
Anyone know how to solve this?

Related

Can I reduce duplication in the serialization of a ruby hash?

I have a hash of the format
{com: 1234, users: [{nid: 3, sets: [1,2,3,4]}, {nid: 4, sets: [5,6,7,8]}]}
which I am sending to a remote server. I am using the HTTParty gem to do this. The code looks like this
class Api
include HTTParty
attr_accessor :headers
def initialize
#headers = { 'Content-Type' => 'application/json' }
end
def post_com(hsh)
response = self.class.post('some_url', query: hsh, headers: headers, format: :plain)
end
end
When I do
api = Api.new.post_com({com: 1234, users: [{nid: 3, sets: [1,2,3,4]}, {nid: 4, sets: [5,6,7,8]}]}
at the remote server, the hash is being sent in the following format
POST "/some_url?com=1234&users[][nid]=3&users[][sets][]=1&users[][sets][]=2&users[][sets][]=3&users[][sets][]=4&users[][nid]=4&users[][sets][]=5&users[][sets][]=6&users[][sets][]=7&users[][sets][]=8
This means for every entry in set, duplicate characeters users[][sets][] are being sent. In operation, there can be many entries in set, and the result is the server rejects the post as having too many characters.
Is there anyway I can have the hash serialized with far less duplication. For instance if I just do
{com: 1234, users: [{nid: 3, sets: [1,2,3,4]}, {nid: 4, sets: [5,6,7,8]}]}.to_json
I receive
"{\"com\":1234,\"users\":[{\"nid\":3,\"sets\":[1,2,3,4]},{\"nid\":4,\"sets\":[5,6,7,8]}]}"
which has far fewer characters.
HTTParty, by default, converts the :query hash into what it calls 'rails style query parameters':
For a query:
get '/', query: {selected_ids: [1,2,3]}
The default query string looks like this:
/?selected_ids[]=1&selected_ids[]=2&selected_ids[]=3
Since you are doing a POST, it is possible/preferable to send your hash in the body of the request rather than in the query string.
def post_com(hsh)
self.class.post('some_url', body: hsh.to_json, headers: headers, format: :plain)
end
This has the advantage that it doesn't do any transformation of the payload and the query string length limit doesn't apply anyway.
For the record, you can disable the default 'rails style' encoding like this:
class Api
include HTTParty
disable_rails_query_string_format
...
end
You can also roll your own custom query string normalizer by passing a Proc to query_string_normalizer.

ElasticSearch handling Rails route params with nested objects

We have a Rails search route which can accept nested objects that should map to ElasticSearch operators.
For example:
{
name: "John",
age: {
{gte: 20}
}
}
The problem is that the SearchKick library throws an error when the Rails route params look like the following:
{"name"=>["Sam Terrick", "John Terrick"], "age"=>{"gte"=>"20"}}
The Searchkick library maps through these filters and does a case comparison for :gte, but the hash rocket keys do not match. ActiveSupport::HashWithIndifferentAccess doesn't get the job done.
https://github.com/ankane/searchkick/blob/master/lib/searchkick/query.rb
Is there an elegant way to handle this transformation of nested objects from the route params without having to check if each param is a Hash?
For that you could make use of the Rails Hash.html#method-i-deep_transform_keys:
params = {"name"=>["Sam Terrick", "John Terrick"], "age"=>{"gte"=>"20"}}
p params.deep_transform_keys(&:to_sym)
# {:name=>["Sam Terrick", "John Terrick"], :age=>{:gte=>"20"}}
But Rails also implements other handy method, more accurate in this case, Hash.html#deep_symbolize_keys:
p params.deep_symbolize_keys
# # {:name=>["Sam Terrick", "John Terrick"], :age=>{:gte=>"20"}}
Same result.

Rails query by arbitrary column

In my Rails API / Angular app, I want to be able to search Rails tables using field values. I currently have this code below working, which allows searching the users table by email id, and it returns the users record as JSON.
api/controllers/users_controller.rb
def query # by email
queried_user = User.where(email: params[:email]).first
if !queried_user.nil?
render json: queried_user, root: false
else
render json: {error: 'Does not exist'}, status: :not_found
end
end
config/routes.rb
get 'api/users/:id/query' => 'api/users#query'
Example url
http://0.0.0.0:8080/api/users/1/query?email=testuser1#example.com
Example returned JSON
{"id":14,"title":"Dr.","first_name":"John","last_name":"Smith","email":"testuser1#example.com","job_title":"Head Bioligist","organisation":"NIH","phone_office":null,"city":null,"country":null,"approved":true,"admin":false,"template":false}
This is all working fine at present, but there are two issues I cannot resolve.
I would like the url to not contain an :id I find when I leave the id out of the url, Rails treats the query parameter as the id. I can made it work by hard-coding a fake id, but it doesn't seem like the right answer to me.
I would like to pass an abitary param hash to the query method. It should map the columns based on the hash contents.
if params = {email: 'testuser1#example.com'} then it should work as now, but other desired options might be:
{job_title: 'Manager'}
{city: 'LA', last_name: 'Smith'}
I expect I will change this code, but don't know how to pass arbitrary elements to the where.
queried_user = User.where(email: params[:email])
The where method can accept a hash, therefore you can pass the param hash containing the condition for the query. Just note only equality and range conditions can be used when passing a hash to the where method. Just be sure that in terms of security of your application you are covered. example:
queried_user = User.where(params[:user])
To get rid of the :id in your routes file define a new route similar to this:
match 'api/users/query', to: 'users#query', as 'user_search'
and then use the 'user_search_path' for sending the search to the query action of the users controller.

how to check/test specific field in json using rspec

This is my rspec code:-
it "which has max value" do
get :index, Devise.token_authentication_key => #user.authentication_token, business_id: #business.id, max: '1'
expect(request.flash[:alert]).to eq(nil)
expect(response.body).to eq([#location].to_json(LocationFinder::API_PARAMS.merge(:root => false)))
end
and testing result is-
expected: "[{\"address\":\"1120 Milky Way\",\"business_id\":1,\"city\":\"Cupertino]"
got: "[{\"address\":\"1120 Milky Way\",\"business_id\":1,\"city\":\"Cupertino,\"distance\":260.33452958767384,]"
Here Distance is an extra field , how can i check particular fields or if it is not possible , how to eliminate "distance" field which is not check by rspec.
You could check individual fields using something like:
# get the first entry in the JSON array
json_response = JSON.parse(response.body).first
# compare each field
expect(json_response['address']).to eq(#location.address)
expect(json_response['business_id']).to eq(#location.business_id)
expect(json_response['city']).to eq(#location.city)
Of course you may need to adjust the exact methods you call on #location depending on your implementation, but that's the gist of it.

rails params hash to String:String hash

I am doing an http get using the url http://localhost/add?add_key[0][key]=1234&add_key[0][id]=1.
I have a rails app which gives me a neat params hash {"add_key"=>{"0"=>{"key"=>"1234", "id"=>"1"}}. However when I try to post this to a different server using
new_uri = URI.parse("http://10.10.12.1/test")
res = Net::HTTP.post_form new_uri,params
The server handling the post is seeing this parameter in the request
{"add_key"=>"0key1234id1"}
Looks like post_form requires a String to String hash. So how do I convert the params hash to
{"add_key[0][key]" => "1234", add_key[0][id]" => "1"}
From the fine manual:
post_form(url, params)
Posts HTML form data to the specified URI object. The form data must be provided as a Hash mapping from String to String.
So you're right about what params needs to be.
You could grab the parsed params in your controller:
{"add_key"=>{"0"=>{"key"=>"1234", "id"=>"1"}}
and then recursively pack that back to the flattened format that post_form expects but that would be a lot of pointless busy work. An easy way to do this would be to grab the raw URL and parse it yourself with URI.parse and CGI.parse, something like this in your controller:
u = URI.parse(request.url)
p = CGI.parse(u.query)
That will leave you with {"add_key[0][key]" => "1234", "add_key[0][id]" => "1"} in p and then you can hand that p to Net::HTTP.post_form.

Resources