Title: Angular 4 & Rails 5 Post Request JSON Formatting
Tags: ruby-on-rails;angular;typescript;rails-api
Question: I'm developing an angular app using a rails backend. I'm having problems formatting the parameters hash so rails can use it. The data is a many to many relationship, and the form contains nested attributes. In Rails, my models utilize the accepts_nested_attributes_for helper. I know exactly what format rails expects, but when I make a POST request, there is one minor detail that's off.  I'm going to list below two param hashes. One is what Angular produces, and the other is what Rails expects.
What's off about the Angular request is rails expects a deeper layer of nesting in the expense_expense_categories attributes. I've never understood why rails requires it. What angular produces looks logical to me.  My question is.. What do I need to do to format the parameters in Angular? Looking at what I have so far, am I doing this in a way that satisfies Angular best practices?
Angular:
"expense": {
"date": "2017/4/13",
"check_number": "132",
"debit": "0",
"notes": "har",
"amount": "24",
"payee_id": "334"
},
"expense_expense_categories_attributes": [{
"expense_category_id": "59",
"amount": 12
},
{
"expense_category_id": "62",
"amount": 11
}
What Rails expects:
"expense": {
"date": "2017/12/12",
"check_number": "122",
"debit": "0",
"notes": "har",
"amount": "24",
"payee_id": "334",
"expense_expense_categories_attributes": {
"210212312": {
"expense_category_id": "72",
"amount": "12"
},
"432323432": {
"expense_category_id": "73",
"amount": "12"
}
}
My code in angular is as follows.
onSubmit() method in component:
onSubmit() {
this.expenseService.addExpense(this.expenseForm.value)
.subscribe(
() =&gt; {
this.errorMessage = '';
},
error =&gt;  {
this.errorMessage = <any&gt;error;
}
);
this.expenseForm.reset();
addExpense in my service file:
addExpense(expense: Expense): Observable<any&gt; {
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers});
return this.http.post('http://localhost:3000/expenses', expense, options)
.map(
(res: Response) =&gt; {
const expenseNew: Expense = res.json();
this.expenses.push(expenseNew);
this.expensesChanged.next(this.expenses.slice());
})
.catch(this.handleError);
my main form:
private initForm() {
let expense_expense_categories_attributes = new FormArray([]);
this.expenseForm = this.fb.group({
id: '',
date: '',
amount: '',
check_number: '',
debit: '',
payee_id: '',
notes: '',
expense_expense_categories_attributes: expense_expense_categories_attributes
});
My FormArray for nested attributes:
onAddExpenseCategories() {
(<FormArray&gt;this.expenseForm.get('expense_expense_categories_attributes')).push(
new FormGroup({
'expense_category_id': new FormControl(null, Validators.required),
'amount': new FormControl(null, [
Validators.required
])
})
);
UPDATE:  I was able to get it working, but I had to use a god awful regex to manipulate the request to what I wanted.  It was an extremely ugly option so I still need to find a better option.  Is there a better way to format JSON Objects and replace the contents?  I'm not sure the correct way to do it. Need help.
Comment: @Jon It seems to be enabled by default with Rails in API mode

Comment: OK.  I see.  So if i do something like `wrap_parameters :expense, include: [:expense_expense_categories]` it should work?

Comment: Have you tried using `wrap_parameters` for `:json`? http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html

Comment: Yes, but by default it will only wrap attributes of the model itself. If you're passing additional attributes such as `expense_categories_attributes` then you will need to explicitly add these to the wrapped params.

Comment: Give it a play - I wouldn't know for certain without testing it. For reference, here is an open issue regarding this very problem - https://github.com/rails/rails/issues/17216

Here is the accepted answer: You need to add the expense_expense_categories to the wrap_parameters like this:
wrap_parameters :expense, include: [:expense_expense_categories_attributes]
Additional attributes must be explicitly added  to wrap_parameters as it only wraps attributes of the model itself by default.
Comment for this answer: I had to list all of my expense attributes in the include: array as well.  I figured it would do that by default. That's why Jon's comment above didn't work. Thank you.


{'source': 'StackExchangeDataset', 'subset': 'stackoverflow.com', 'metadata': {'AnswerCount': 1, 'CommentCount': 5, 'ContentLicense': 'CC BY-SA 3.0', 'CreationDate': '2017:00:04/18/17', 'FavoriteCount': None, 'PostTypeId': 1, 'Tags': '<ruby-on-rails><angular><typescript><rails-api>', 'Title': 'Angular 4 & Rails 5 Post Request JSON Formatting', 'ViewCount': 660, 'id': 43482480, 'user_AboutMe': '<p>I am a web developer currently working with small businesses to create simple sites with back-end features that help clients manage communications via social and email channels.  Currently using PHP, but I am hard at work changing my workflow to Ruby on Rails.</p>\n', 'user_AccountId': 933742.0, 'user_DisplayName': 'ctilley79', 'user_Reputation': 2144.0, 'user_Views': 244.0, 'user_id': 963080.0}}