angular2 formBuilder group causes nesting of params - ruby-on-rails

I have the following formBuilder in angular2:
constructor(
private formBuilder: FormBuilder) {
this.form = formBuilder.group({
id: [],
title: ['', Validators.required],
dates: formBuilder.group({
start_date: ['', Validators.required],
end_date: ['', Validators.required]
}, {validator: this.checkDates})
});
}
dates is in a separate group, this is for validation purposes. onSubmit calls this service method:
update(academicTerm: AcademicTerm): Observable<AcademicTerm> {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
return this.http
.patch(this.endpointUrl + academicTerm.id, JSON.stringify(academicTerm), {headers})
.map(this.extractData)
.catch(this.handleError);
}
When I check the backend (Rails5 API server) I can see this param set:
Parameters: {"id"=>"3", "title"=>"Term Title", "dates"=>{"start_date"=>"2016-11-27", "end_date"=>"2016-12-01"}, "academic_term"=>{"id"=>"3", "title"=>"Term CL"}}
Note in the academic_term hash that start_date and end_date are not present.
On the Rails side of things I have strong params set up like this:
def academic_term_params
params.require(:academic_term).permit(:id, :title, :start_date, :end_date)
end
I have tried setting the nested dates object in strong params:
def academic_term_params
params.require(:academic_term).permit(:id, :title, :dates => [:start_date, :end_date])
end
Which has no affect (dates is not an associated attribute?). So while I can update title I cannot update the dates.
Is there a way to flatten the params sent from angular to be something like this:
Parameters: {"id"=>"3", "title"=>"Term Title", "start_date"=>"2016-11-27", "end_date"=>"2016-12-01"}
Or is there a way to fix it on the Rails side?

You can flatten the object before sending the request to the server.
update(academicTerm: AcademicTerm): Observable<AcademicTerm> {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
academicTerm['start_date'] = academicTerm.dates.start_date;
academicTerm['end_date'] = academicTerm.dates.end_date;
// delete academicTerm.dates; // optional
return this.http
.patch(this.endpointUrl + academicTerm.id, JSON.stringify(academicTerm), {headers})
.map(this.extractData)
.catch(this.handleError);
}

Related

using react and json to upload images to rails

I have a form in a react compnent that includes an image. This component has an associated state for the form, it is quite a large and complex form with many nested arrays of sub elements that vary in length, which is why I do not want to use FormData().
state = {
recipe: {
title: "",
image: null,
ingredient_groups: [
{
title: "",
ingredients: [
{
name: "",
measurement_metric: "",
measurement_imperial: ""
}
]
}
]
}
The state is updated with an onChange handler for the input field like the following
let {recipe} = this.state;
recipe.image = event.target.files[0];
this.setState({recipe});
This is then sent via an axios post request,
let {recipe} = this.state;
axios.post('/api/v1/recipe', {recipe: recipe}).then((result) => {
console.log(result.status)
})
in rails I have a params method set up for the allowed params
def recipe_params
params.require(:recipe).permit(:title,
:image,
{ingredient_groups: [
:id,
:title,
{ingredients: [
:id,
:name,
:measurement_metric,
:measurement_imperial
]}
]})
end
however I am getting nil for recipe_params[:image] when I try to save it into active storage like this:
#recipe.image.attach(recipe_params[:image])
Is what Im doing even possible? Or is the only way to transmit this via a FormData object on the post. I would really prefer this to be done via a pure json method.
using https://www.npmjs.com/package/object-to-formdata I was able to successfully change my react state object into a FormData Object, and then post this to my endpoint and it has worked as expected

Use fetch to pass variable in rails

I am creating an app in Rails with Reactjs. I want to pass the value of input field to the controller as a variable so that I can use that variable in def create. How can I do that with fetch?
Use fetch's POST request to your API backend endpoint of your :create method. Make sure that you include your variables in a params payload when POSTing.
Then in your controller, you can access your variables through params
EDIT:
From the fetch api docs example in using POST (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
async function postData(url = '', data = {}) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
return response.json();
}
postData('/your/api/endpoint', { data: 'yourData' }).then(res=> { console.log(res) });
Then in your controller, access { data: 'yourData' } through params like so:
def create
#data = params[:data]
// Do what you want with #data here
end
It's also best to whitelist your params first before using them in your controller.

Rails POST request to Eventbrite not working

I am trying to POST(create) an event using EventBrite's API.
I have the auth key but am having trouble formatting the request.
Here is what I have.
def self.syndicate_event
event = Event.first
body_string =
"{
'event': {
'name': {
'html': #{event.name}
},
'description': {
'html': #{event.description}
},
'start': {
'utc': #{event.start},
'timezone': #{event.start_timezone},
},
'end': {
'utc': #{event.end},
'timezone': #{event.end_timezone},
},
'currency':#{event.currency}
}
}"
json_body = body_string.to_json
respo = HTTP.auth("Bearer mytoken")
.post("https://www.eventbriteapi.com/v3/events/",
params: json_body
)
end
It gives me : 'Can't convert String into Array.' error. Any ideas what is going on ? And if someone has used the API for EventBrite, is there a better way other than formatting my string like that and then making it into JSON.
Thanks
I'm not familiar with the EventBrite API, but it looks like the json_body string is malformed. You can verify that your json is valid by doing JSON.parse(json_body) and that should return a hash representation of your json. If it's malformed, it will raise an error.
I would opt to use the to_json method on a hash instance to guarantee the json will not be malformed.
Something like:
body_object =
{ event: {
name: {
html: event.name
},
description: {
html: event.description
}...,
currency: event.currency }
}
json_body = body_object.to_json

How to send params from nested forms?

I'm making a POST request from a nested form which is written in reactjs such that it is making an ajax request to create method of the products controller.
React Code:
I have an empty object in the getInitialState like this
getInitialState: function() {
return {
products: [{name: '', price: '', quantity: ''}],
count: 1
};
},
When i submit the form,
handleSubmit: function(e) {
e.preventDefault();
var productsArray = this.state.products;
$.ajax({
data: {
product: productsArray
},
url: '',
type: "POST",
dataType: "json",
success: function ( data ) {
console.log(data);
// this.setState({ comments: data });
}.bind(this)
});
},
the object gets populated and the parameter hash becomes like this
Parameters: {"product"=>{"0"=>{"name"=>"", "price"=>"", "quantity"=>""}}, "shop_id"=>"gulshop"}
So i'm getting
ActiveRecord::UnknownAttributeError (unknown attribute '0' for Product.):
How can i get the parameter hash like this:
Parameters: {"product"=>[{"name"=>"", "price"=>"", "quantity"=>""}], "shop_id"=>"gulshop"}
What can be done for it ?
Your original error 'unknown attribute '0' for Product.' is because the Product class does not have an attribute '0'. I'm not sure where the '0' is coming from as you haven't posted your react code that makes the request.
You can easily make a request from your component using jQuery's .ajax method. e.g
$.ajax({
type: 'POST',
url: '/your_url',
data: {
course: {
name: 'Hello World',
price: 120
}
}
});
You would then have something like the following in your controller..
class ProductController < ApplicationController
def create
#product = Product.create(product_params)
end
private
def product_params
params.require(:product).permit(:name, :price)
end
end

RSpec request test merges hashes in array in POST JSON params

Looks like a bug in RSpec but maybe I'm missing something.
I have a request spec where I post a JSON that contains an array of hashes:
spec/requests/dummy_request_spec.rb:
post "http://my.server.com/some/route", {
format: :json,
data: [
{
details: {
param1: 1
},
},
{
details: {
param2: 1
}
}
]
}
For some odd reason, RSpec merges the hashes into one element and then sends them to server.
print out of params received in controller:
data: [
{
details: {
param1: 1,
param2: 2
},
},
]
versions:
rspec-2.13.0
rails-3.2.10
Very strange!!
Thanks
Got it! array of hashes is not supported for form-data
RSpec by default posts it as form-data. Solution:
post '...', {...}.to_json, {'CONTENT_TYPE' => "application/json", 'ACCEPT' => 'application/json'}
Also, be aware that you have an extra comma:
data: [
{
details: {
param1: 1
}**,**
},
{
details: {
param2: 1
}
}
]
I faced the same problem reported in the question post while using following versions
ruby 2.3.2
rails (5.0.0.1)
rspec-rails (3.5.2)
Searching for the problem on web I found a related issue at https://github.com/rails/rails/issues/26069 and the solution suggested by it is to pass as: :json option to the post, get etc methods while using them in the controller tests (refer the PR referenced in comment https://github.com/rails/rails/issues/26069#issuecomment-240916233 for more details). Using that solution didn't solved the params mingling issue I was encountering. Following were the results found for different types of data I used with the recommended solution:
In my controller spec I have following
before(:each) do
request.accept = "application/json"
end
and in the test logs I do see that the request is being served
Processing by Api::V1::MyController#my_action as JSON
Data-1
data = [
{
param_1: "param_1_value",
},
{
param_2: "param_2_value",
}
]
params.merge!(my_data: data)
post :my_action, params: params, as: :json
That ends-up in request params as following
{ "my_data"=> [ {"param_1"=>"param_1_value", "param_2"=>"param_2_value"} ] }
which is wrong.
Data-2
data = [
{
param_1: "param_1_value",
something_else: ""
},
{
param_2: "param_2_value",
another_thing: ""
}
]
params.merge!(my_data: data)
post :my_action, params: params, as: :json
That ends-up in request params as following
{ "my_data"=> [ {"param_1"=>"param_1_value", "something_else"=>"", "another_thing"=>"", "param_2"=>"param_2_value"} ] }
which is wrong.
Data-3
data = [
{
param_1: "param_1_value",
param_2: ""
},
{
param_1: ""
param_2: "param_2_value",
}
]
params.merge!(my_data: data)
post :my_action, params: params, as: :json
That ends-up in request params as following
{ "my_data"=>[ {"param_1"=>"param_1_value", "param_2"=>""}, {"param_1"=>"", "param_2"=>"param_2_value"} ] }
which is correct.
It should be noted that for Data-3 without the as: :json option also I receive the correct data in request params.
One more thing: In comment https://github.com/rails/rails/issues/26069#issuecomment-240358290 an alternate solution suggested to deal with the problem narrated above is following
another fix would be to specify nested attributes not as array but as
hash:
params = {
id: #book.id,
book: {
title: 'Cool',
pages_params: {
"0" => { id: #page.id, content: 'another content' },
"1" => { id: #delete_page.id, _destroy: 1 },
"2" => { content: 'another new page' }
}
},
format: :json
}
Unfortunately this was removed from the documentation of nested
attributes so I don't know if this is going to stay valid.
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
But this solution has a drawback is that we need to manually sanitize the data on controller-end to bring it back to expected structure i.e. Array of Hashes.
Finally I am sharing below what worked for me
spec/shared_contexts.rb
RSpec.shared_context "common helpers" do
def set_request_header(request_obj:, request_header_name:, request_header_value:)
request_obj.headers[request_header_name] = request_header_value
end
def set_request_header_content_type_as_json(request_obj:)
set_request_header(request_obj: request_obj, request_header_name: 'CONTENT_TYPE', request_header_value: 'application/json')
end
end
Then in my spec file
require 'shared_contexts'
RSpec.describe Api::V1::MyController, :type => :controller do
include_context "common helpers"
context "POST #my_action" do
it "my example" do
data = [
{
param_1: "param_1_value",
},
{
param_2: "param_2_value",
}
]
params.merge!(my_data: data)
set_request_header_content_type_as_json(request_obj: request)
post :my_action, params: params
end
end
end
As can be seen setting the request header CONTENT_TYPE was what was missing to make the request params to be received in expected structure.

Resources