How to send correctly a JSON request testing Rails? - ruby-on-rails

I'm witnessing an odd behavior in Rails, when sending a post request, with the following body:
If you check is a Hash (converted to JSON when sending). But ODDLY when being read by params in controller, is recognized like this:
If you check carefully, the attributes of :first_name and :email are moved to the previous item in the array.
I'd thought if you have an array with certain attributes on the first item, but some different attributes on the following items, the array would be respecting the positions of which the attributes are set for.
I know most likely the answer would be "just put a nil or empty value of those attributes on the first item of the array", but I'm more interested in the reason of why this is happening.
Thank you.
UPDATE
Thanks to a question, I replicated the scenario form a browser (I was originally doing the request from the test feature of rails), and checking the Network from the browser, this is what is being sent:
{
"name": "un nombre",
"team_members": [
{
"user_id": 1,
"team_member_role_id": 4
},
{
"email": "cpamerica#avengers.com",
"first_name": "Cap America",
"team_member_role_id": 4,
"admin": true
},
{
"email": "hulk#avenrgers.com",
"first_name": "Bruce Banner",
"team_member_role_id": 1,
"admin": false
},
{
"email": "ironman#avengers.com",
"first_name": "Tony Stark",
"team_member_role_id": 1,
"admin": false
},
{
"email": "thor#avengers.com",
"first_name": "Thor Hijo de Odín",
"team_member_role_id": 2,
"admin": false
}
]
}
And it works. So I focused on how I was sending the info from the test environment, this is the actual code:
team = {
:name => 'un nombre',
:team_members => [
{
:user_id => 1,
:team_member_role_id => TeamMemberRole.role_communicator_id
},
{
:email => 'cpamerica#avengers.com',
:first_name => 'Cap America',
:team_member_role_id => TeamMemberRole.role_communicator_id,
:admin => true
},
{
:email => 'hulk#avenrgers.com',
:first_name => 'Bruce Banner',
:team_member_role_id => TeamMemberRole.role_visionary_id,
:admin => false
},
{
:email => 'ironman#avengers.com',
:first_name => 'Tony Stark',
:team_member_role_id => TeamMemberRole.role_visionary_id,
:admin => false
},
{
:email => 'thor#avengers.com',
:first_name => 'Thor Hijo de Odín',
:team_member_role_id => TeamMemberRole.role_developer_id,
:admin => false
}
]
}
post create_team_path, :team => team, :format => :json
And what is being read in the controller by request.raw, this is what is getting (using the debugger):
team[name]=un+nombre&
team[team_members][][user_id]=1&
team[team_members][][team_member_role_id]=4&
team[team_members][][email]=cpamerica%40avengers.com&
team[team_members][][first_name]=Cap+America&
team[team_members][][team_member_role_id]=4&
team[team_members][][admin]=true&
team[team_members][][email]=hulk%40avenrgers.com&
team[team_members][][first_name]=Bruce+Banner&
team[team_members][][team_member_role_id]=1&
team[team_members][][admin]=false&
team[team_members][][email]=ironman%40avengers.com&
team[team_members][][first_name]=Tony+Stark&
team[team_members][][team_member_role_id]=1&
team[team_members][][admin]=false&
team[team_members][][email]=thor%40avengers.com&
team[team_members][][first_name]=Thor+Hijo+de+Od%C3%ADn&
team[team_members][][team_member_role_id]=2&
team[team_members][][admin]=false&
format=json
Any idea on why the index of each team_member is missing? Am I sending the array wrong?

To send an acceptable json request you need to send as a formed String instead the hash, also add the headers
your code must look something like this:
post create_team_path, {:team => team}.to_json, 'CONTENT_TYPE' => 'application/json;charset=utf-8'
Note the problem is within the parser hash to form-request not adding the index of each object
Source: How to post JSON data in rails 3 functional test

Related

Ruby on Rails parse api to save to db

I am practicing with apis in RoR. I am trying to save only a few items from the api call id, length, dip, name but how do I parse it and save the fields that I need and do they need to be in params? Currently the api call data is not in params.
On button click I want to have those fields listed above save into the db
routes
root 'welcome#index'
post 'search_campaigns', to: 'campaigns#search_all_campaigns'
my model
class Campaign < ApplicationRecord
def self.get_your_campaigns
uri = URI.parse("https://example.site/api/v2/users")
request = Net::HTTP::Get.new(uri)
request.content_type = "application/json"
request.basic_auth("example#email.com", "238urfs393kmdsb2189aead01")
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
return JSON.parse(response.body)
end
end
controller
class CampaignsController < ApplicationController
def search_all_campaigns
#campaigns = Campaign.get_your_campaigns
redirect_to root_path
end
end
view
<%= button_to 'Get All Campaigns', search_campaigns_path %>
This how the api call data looks
[{"id"=>2758, "dip"=>"2.0", "length"=>10, "name"=>"Cereal", "total_remaining"=>100, "status"=>6, "is_retarget"=>false}, {"id"=>278563, "dip"=>"1.25", "length"=>2, "name"=>"Pizza", "total_remaining"=>123, "status"=>6, "supplier_link"=>"http://www.developingmedia.com/adhoc.php?id=", "incidence"=>50, , "days_in_field"=>5, "max_daily_completes"=>nil, "is_retarget"=>false}, {"id"=>278564, "dip"=>"4.25", "length"=>25, "name"=>"California", "days_in_field"=>5,}]
You say the API and therefore you Campaign.get_your_campaigns method returns a Hashthat looks like this:
[
{
"id" => 2758,
"dip" => "2.0",
"length" => 10,
"name" => "Cereal",
"total_remaining" => 100,
"status" => 6,
"is_retarget" => false
},
{
"id" => 278563,
"dip" => "1.25",
"length" => 2,
"name" => "Pizza",
"total_remaining" => 123,
"status" => 6,
"supplier_link" => "http://www.developingmedia.com/adhoc.php?id=",
"incidence" => 50, ,
"days_in_field" => 5,
"max_daily_completes" => nil,
"is_retarget" => false
},
{
"id" => 278564,
"dip" => "4.25",
"length" => 25,
"name" => "California",
"days_in_field" => 5,
}
]
You can use Hash#slice to extract only the attributes you are interested in. And then pass those attributes one after the other to the create method:
campaigns_hashes = Campaign.get_your_campaigns
campaigns_attributes = campaigns_hashes.map { |hash| hash.slice(:id, :name, :length, :dip) }
campaigns = campaigns_attributes.each { |attributes| Campaign.create(attributes) }
Note: You will very likely need to add some error handling to this, for example, to deal with invalid data returned from the API or the handle records that have already been imported to avoid duplicates.

Slack is not correctly formatting my JSON for a slash command

I am setting up a custom slash command between Slack and Build kite using Ruby. I am getting the correct API calls and the information is being passed. Everything seems to be working but when I try to set up the json payload I receive I can not seem to use Slacks new formatting. I have it working with the old formatting but when I switch to new it breaks and just gives me a string.
I have tried parsing the json, ensuring it is valid, and using other methods like response.body.
This JSON will result in a string:
[{
"type":"section",
"text":
{
"type":"mrkdwn",
"text":"*Deploy History:* Deploys in the last 6 hours"
}
}]
This JSON will correctly be formatted:
{
text: "",
attachments: [{
title: 'Deploy History',
text: 'Deploys in the last 6 hours',
:fields => [{
:title => 'Message',
:value => build_list[0].message,
:short => true
}, {
:title => 'Name',
:value => build_list[0].creator.name,
:short => true
}, {
:title => 'Finished at',
:value => build_list[0].finished_at,
:short => true
}],
color: 'good'
}]
}
The expected result of the first code would look nice and pretty but it just spits this out: {"type":"section","text":{"type":"mrkdwn","text":"Deploy History: Deploys in the last six hours"}}]
The result of the working bottom code is nice and pretty
The file that is called when the slash command is sent
module SlackLine::Commands
module DeployHistory extend self
def deploy_history
build_list = buildkite.process_history_event
presenter.deploy_history(build_list)
end
def presenter
SlackLine::Presenters::DeployHistoryPresenter
end
def buildkite
SlackLine::Services::BuildkiteHistoryService
end
end
end
This is the file where we make the api call to buildkite
def deploy_stats
finished_from = 6.hours.ago.to_time.iso8601
options = {
branch: 'master',
finished_from: finished_from,
state: 'finished'
}
build_list = client.pipeline_builds('calendly', 'calendly', options)
build_list
end
Solved! All I had to do was add a block. Thank you for bearing with me and I hope this helps someone out!
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Deploys in the last six hours"
}
}

Grabbing a specific hash in an array that meets a specific criteria

I have a huge array full of a bunch of hashes. What I need to do is single out one index hash from the array that meets a specific criteria. (doing this due to an rspec test, but having trouble singling out one of them)
My array is like this
[
{
"name" => "jon doe",
"team" => "team2",
"price" => 2000,
"eligibility_settings" => {}
},
{
"name" => "jonny doe",
"team" => "team1",
"value" => 2000,
"eligibility_settings" => {
"player_gender" => male,
"player_max_age" => 26,
"player_min_age" => 23,
"established_union_only" => true
}
},
{
"name" => "jonni doe",
"team" => "team3",
"price" => 2000,
"eligibility_settings" => {}
},
]
I need to single out the second one, based on its eligibility settings. I just took three of them from my array, have lots more, so simple active record methods like (hash.second) won't work in this instance.
I've tried things like
players.team.map(&:hash).find{ |x| x[ 'eligibility_settings?' ] == true}
However when I try this, I get a nil response. (which is odd)
I've also looked into using the ruby detect method, which hasn't gotten me anywhere either
Players.team.map(&:hash).['hash.seligibiltiy_settings'].detect { true }
Would anybody have any idea what to do with this one?
Notes
players.team.map(&:hash).find{ |x| x[ 'eligibility_settings?' ] == true}
Players.team.map(&:hash).['hash.seligibiltiy_settings'].detect { true }
Is is players or Players ?
Why is it plural?
If you can call map on team, it probably should be plural
Why do you convert to a hash?
eligibility_settings? isn't a key in your hash. eligibility_settings is
eligibility_settings can be a hash, but it cannot be true
If you want to check if it isn't empty, use !h['eligibility_settings'].empty?
Possible solution
You could use :
data = [
{
'name' => 'jon doe',
'team' => 'team2',
'price' => 2000,
'eligibility_settings' => {}
},
{
'name' => 'jonny doe',
'team' => 'team1',
'value' => 2000,
'eligibility_settings' => {
'player_gender' => 'male',
'player_max_age' => 26,
'player_min_age' => 23,
'established_union_only' => true
}
},
{
'name' => 'jonni doe',
'team' => 'team3',
'price' => 2000,
'eligibility_settings' => {}
}
]
p data.find { |h| !h['eligibility_settings'].empty? }
# {"name"=>"jonny doe", "team"=>"team1", "value"=>2000, "eligibility_settings"=>{"player_gender"=>"male", "player_max_age"=>26, "player_min_age"=>23, "established_union_only"=>true}}
If h['eligibility_settings'] can be nil, you can use :
data.find { |h| !h['eligibility_settings'].blank? }
or
data.find { |h| h['eligibility_settings'].present? }

Collection is serializing with the each-object key being the pluralized key, cannot figure out why

We've got a simple api endpoint that works like so:
def index
render json: Country.all
end
This is unfortunately giving us this output:
{
"countries" => [
[0] {
"countries" => {
"id" => 1,
"iso" => "US",
"iso3" => "USA",
"iso_name" => "UNITED STATES",
"name" => "United States of Foo",
"numcode" => 840
}
},
[1] {
"countries" => {
"id" => 2,
"iso" => "CA",
"iso3" => "CAN",
"iso_name" => "CANADA",
"name" => "Canada",
"numcode" => 124
}
}
]
}
Notice that the key for each individual object is the plural form of the key.
However, when we set the endpoint to work like so
def inded
render json: {countries: Country.all}
end
The output looks like so:
{
"countries" => [
[0] {
"country" => {
"id" => 1,
"iso" => "US",
"iso3" => "USA",
"iso_name" => "UNITED STATES",
"name" => "United States of Foo",
"numcode" => 840
}
},
[1] {
"country" => {
"id" => 2,
"iso" => "CA",
"iso3" => "CAN",
"iso_name" => "CANADA",
"name" => "Canada",
"numcode" => 124
}
}
]
}
I.e., correct.
However, setting the key like {countries: Country.all} is bad form, and I'd like to understand why rails is serializing each element with the collection key rather than the object key (that is, why it's plural and not singular for each country).
We have not overridden to_json or any other serialization methods. We are using the default rails model serializer (I tried making an explicit serializer, but there was no change in behavior). I cannot, for the life of me, figure out why it is pluralizing these keys.
Edit: There is even more weirdness. I was incorrect about the explicit serializer, when I set up a serializer (with just the attributes as it normally displays), I get this:
{
"countries" => [
[0] {
"id" => 1,
"iso" => "US",
"iso3" => "USA",
"iso_name" => "UNITED STATES",
"name" => "United States of Foo",
"numcode" => 840
},
[1] {
"countries" => {
"id" => 2,
"iso" => "CA",
"iso3" => "CAN",
"iso_name" => "CANADA",
"name" => "Canada",
"numcode" => 124
}
}
]
}
The first object has no key, and every other one is plural. I tested both in tests, and confirmed by making the actual API call.
I can't find anything that would override this other than an overridden <=> and to_s. These should not affect output in this way?
Mike, I think you copied and pasted something wrong, or you might just be confused.
Both outputs have the same results: Countries (Plural) as the main response, and country(Singular) for each individual inside countries. Or am I misreading?
I think what's happening here is that you're trying to specify the root key in a way that AM::S doesn't support.
Instead of render json: { countries: Country.all }, I'd recommend trying render json: Country.all, root: "countries" (specifying the root key is unnecessary if you're calling the serializer from within CountriesController, otherwise you need it to get the functionality you desire).
See https://github.com/rails-api/active_model_serializers/tree/0-8-stable#arrays for more information.

Why is the order of execution of attribute assignments failing?

I am passing these parameters to a controller:
{
"utf8" => "✓",
"authenticity_token" => "ersjaJ4/ieZelVifP/YpBHTJtiQ53HgO5KYjEdW0BlQ=",
"transaction" => {
"use_balance" => "1",
"traces_attributes" => {
"trace_ids" => ["6"],
"6" => {
"amount" => "12.0",
"charge_id" => "6"
}
},
"positive_balance" => "12",
"property_id" => "2",
"community_id" => "1"
},
"commit" => "Save Payment",
"community_id" => "1",
"property_id" => "2"
}
The controller#create then:
#payment = Transaction.new(params[:transaction])
Then the Transaction model:
belongs_to :property
belongs_to :community
attr_accessible :positive_balance
def traces_attributes=(params)
#INSIDE HERE THE VALUES OF
#params[:trace_ids] => ['6'] OK
#BUT
#self.possitive_balance => "" **NOT OK**
#self.property_id => nil **NOT OK**
end
My hypothesis is that traces_attribute= is executed before positive_balance= and property_id
Can I change this?
Why is this failing?
The order of the assignments should be the same as the order of the params in the form, but I don't think this is guaranteed.
A safer solution would be to only store the data in the traces_attributes= method, and access the other attributes later, for example in a before_save callback.
it looks based on the transaction hash that the property_id is outside of that hash so if you are building based on transactions it won't have a property_id
"transaction"=>{"use_balance"=>"1",
"traces_attributes"=>{"trace_ids"=>["6"],
"6"=>{"amount"=>"12.0",
"charge_id"=>"6"
}
},
"positive_balance"=>"12",
"property_id"=>"2",
"community_id"=>"1"
},
"commit"=>"Save Payment",
"community_id"=>"1",
"property_id"=>"2"}
do you see what i mean, the number of curly braces is messed up and prop. id isn't ending up in transactions ( i just copy and pasted your code pasted above )

Resources