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(
() => {
this.errorMessage = '';
},
error => {
this.errorMessage = <any>error;
}
);
this.expenseForm.reset();
}
addExpense in my service file:
addExpense(expense: Expense): Observable<any> {
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) => {
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>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.
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.
Related
Question of the day: how to encode URL to be able to pass complex data on a GET request?
# data to pass
{
"main_key": {
"other_key": {
"main_array": [{
"name": "Bob",
"nickname": "bobby"
},
{
"name": "Tom",
"nickname": "Tommy"
}
]
}
}
}
Here is the current solution I got with Postman
Here is the current Rails interpretation of such a query, which is correct.
# Rails server side
Parameters: {"main_key"=>{"other_key"=>{"main_array"=>[{"name"=>"Bob", "nickname"=>"bobby"}, {"name"=>"Tom", "nickname"=>"tommy"}]}}, "default"=>{"format"=>:json}}
Can anybody have a better way to achieve a request with such a complex nested array object?
The other solution I got is to pass JSON directly as value of a query parameter and then parse it from the controller.
**Edit: ** I can pass this json on the body of the request but as it's a GET method, it does not respect XHR requirements.
If you have jquery, you can use .param() method of it.
let myParams = {
"main_key": {
"other_key": {
"main_array": [{
"name": "Bob",
"nickname": "bobby"
},
{
"name": "Tom",
"nickname": "Tommy"
}
]
}
}
}
console.log($.param(myParams));
This will give you your desired string.
"main_key%5Bother_key%5D%5Bmain_array%5D%5B0%5D%5Bname%5D=Bob&main_key%5Bother_key%5D%5Bmain_array%5D%5B0%5D%5Bnickname%5D=bobby&main_key%5Bother_key%5D%5Bmain_array%5D%5B1%5D%5Bname%5D=Tom&main_key%5Bother_key%5D%5Bmain_array%5D%5B1%5D%5Bnickname%5D=Tommy"
I started exploring Google Docs API in Python. It does pretty much everything I want it to do except for one thing.
I can replace the text of a document but I can't change the value of the hyperlinks.
Meaning if a link looks like this : a link, I can change the value of the text a link but not the target URL.
I've been going through the documentation but I can't find anything about it. Could it be a missing feature or am I missing the way to do that?
You can modify the hyperlink using UpdateTextStyleRequest of the batchupdate method in Google Docs API. At this time, please set the property of Link of TextStyle.
Endpoint
POST https://docs.googleapis.com/v1/documents/{file ID}:batchUpdate
Request body:
{
"requests": [
{
"updateTextStyle": {
"textStyle": {
"link": {
"url": "https://sampleUrl" # Please set the modified URL here.
}
},
"range": {
"startIndex": 1,
"endIndex": 2
},
"fields": "link"
}
}
]
}
Note:
From your question, I could understand that you have already used Google Docs API and you can modify the text of the link text. I think that you can modify the link using above request body and the script you have.
References:
UpdateTextStyleRequest
TextStyle
Link
If this was not useful for your situation, I apologize.
Edit:
You want to retrieve the text with the hyperlink.
From your reply comment, I could understand like above. When my understanding is correct, you can retrieve it using documents.get method. In this case, when fields is used, the response become to easily read.
Endpoint:
GET https://docs.googleapis.com/v1/documents/{file ID}?fields=body(content(paragraph(elements(endIndex%2CstartIndex%2CtextRun(content%2CtextStyle%2Flink%2Furl)))))
In this endpoint, body(content(paragraph(elements(endIndex,startIndex,textRun(content,textStyle/link/url))))) is used as fields.
Sample response:
For example, when the following texts are put in a Google Document and def has a hyperlink,
abc
def
The response is as follows. From the following result, you can retrieve the position of text with the hyperlink can be retrieved. Using this, you can modify the hyperlink.
{
"body": {
"content": [
{},
{
"paragraph": {
"elements": [
{
"startIndex": 1,
"endIndex": 5,
"textRun": {
"content": "abc\n",
"textStyle": {}
}
}
]
}
},
{
"paragraph": {
"elements": [
{
"startIndex": 5,
"endIndex": 8,
"textRun": {
"content": "def",
"textStyle": {
"link": {
"url": "https://sample/"
}
}
}
},
{
"startIndex": 8,
"endIndex": 9,
"textRun": {
"content": "\n",
"textStyle": {}
}
}
]
}
}
]
}
}
Reference:
documents.get
batchUpdate requires to know position of text, we can get document with all content and find positions of links
In my case I implement it as:
Copy template to new place with final name
Replace link texts and other parts of text
Get document
Find links positions in doc
Update link URLs
Here example in nodejs
const {google, docs_v1} = require('googleapis');
async function replaceInDoc(doc) {
let documentId = 'some doc id'
let auth = 'auth value for user'
let linkNewUrl = 'https://github.com/googleapis/google-api-nodejs-client'
google.options({auth: auth})
var docs = new docs_v1.Docs({}, google)
// document should have link with http://repo-url.com text, we will update it
var requests = [
{
replaceAllText: {
containsText: {
text: 'http://repo-url.com',
matchCase: true,
},
replaceText: linkNewUrl,
},
}
]
var updateRes = await docs.documents.batchUpdate({
documentId: documentId,
resource: {
requests: requests,
},
});
var docInfo = await docs.documents.get({documentId: documentId})
var linkPos = findLinksInDoc(docInfo)
// set new url to link by position of link in the document
var requests = [
{
updateTextStyle: {
textStyle: {
link: {
url: linkNewUrl
}
},
range: {
startIndex: linkPos[linkNewUrl][0],
endIndex: linkPos[linkNewUrl][1]
},
fields: "link"
}
}
]
var updateRes = await docs.documents.batchUpdate({
documentId: documentId,
resource: {
requests: requests,
},
});
}
// returns hash as { 'http://example.com': [startPosition, endPosition] }
function findLinksInDoc(doc) {
var links = {}
doc.data.body.content.forEach(section => {
if (section.paragraph) {
section.paragraph.elements.forEach(element => {
if (element.textRun && element.textRun.textStyle.link) {
links[element.textRun.content] = [element.startIndex, element.endIndex]
}
})
}
})
return links
}
I am trying to create a new issue utilizing the JIRA REST API and whenever I try, I get back the following generic error:
{ errorMessages: [ 'Internal server error' ], errors: {} }
I can successfully GET from the API, and the credentials I'm connecting with have full Admin access to JIRA (so it's not an Auth issue), but I get this error every time with POST. Below is a snippet of the JSON data I'm sending. Am I missing anything obvious?
Below is my JavaScript code. Note I'm using jira-connector from npm. (Real domain replaced with mydomain for this sample code)
const JiraClient = require('jira-connector');
const dotenv = require('dotenv').config();
function createNewIssue(fields) {
const encoded = process.env.JIRA_ENCODED_PW;
const jira = new JiraClient({
host: 'mydomain.atlassian.net',
basic_auth: {
base64: encoded
}
});
return new Promise((resolve, reject) => {
jira.issue.createIssue(fields, (error, issue) => {
if (error) {
console.log(error);
reject(error);
} else {
console.log(issue);
resolve(encoded);
}
});
})
}
Below is the JSON that's being passed into fields in the JS above. Note customfield_17300 is a radio button, and customfield_17300 is a multi-select box. For both cases, I've tried using the "id" and also the actual string "name" value. All IDs below were taken straight from a API GET of the same issue in question:
{
"fields": {
"project": {
"id": "13400"
},
"summary": "TEST API TICKET - 01",
"issuetype": {
"id": "11701"
},
"customfield_14804": { "id": "13716" },
"customfield_14607": "Hardware",
"customfield_17300": [
{
"id": "18322"
}
] ,
"customfield_16301": "Customer PO",
"customfield_14800": "LA, California",
"customfield_16302": "FEDEX 234982347g"
}
}
sigh I figured it out... other posts that said this cryptic error was due to a malformed JSON were correct.
In my route, I passed fields as coming from req.body.fields which actually dove into the fields values instead of passing it straight through. This made it so that when the JSON was sent to JIRA the fields outer wrapper was missing. I changed my route to pass along req.body instead of req.body.fields and all was well.
...that was a fun 4 hours...
I'm parsing some JSON from a mixed content source, and with it trying to store it with ActiveRecord.
At the moment I'm using a ton of variables:
json['settings']['newsletters']['weekly']
json['info']['address']['city']
Or trying to make things a little easier:
newsletters = json['settings']['newsletters']
newsletters['weekly']
address = json['info']['address']
address['city']
But this is all getting very messy, and not DRY.
I think the better way to do this would be to iterate over each element that is a hash (and therefore 'complex'), and assign it it's own object. This way, I don't have to declare a trillion variables, they can instead be assigned from the context of the JSON input.
So, I can do something like this:
user = json['input']
user.settings.newsletters.weekly
user.info.address.city
This is inspired by what ActiveResource documents:
# Any complex element (one that contains other elements) becomes its own object:
#
# {"id":1,"first":"Tyler","address":{"street":"Paper St.","state":"CA"}}
tyler = Person.find(1)
tyler.address # => <Person::Address::xxxxx>
tyler.address.street # => 'Paper St.'
Here is the JSON, reduced for brevity's sake:
{
"username": "robert_fitzsimmonds",
"emails": [{
"id_number": 1,
"address": "robert_fitzsimmonds#yahoo.com",
"confirmed": false
}, {
"id_number": 2,
"address": "robert_fitzsimmonds#gmail.com",
"confirmed": true
}],
"settings": {
"marketing": {
"main": true,
"weekly": false,
"daily": false
},
"language": "English"
},
"info": {
"address": {
"line_1": "31 Mole Road",
"line_2": "",
"city": "London",
"post_code": "NE4 5RJ"
},
"shared_account": false
}
}
Would such an iteration be the most efficient solution, or is it best to stick to long, messy variables?
Use the hash_dot gem if you can https://github.com/adsteel/hash_dot
I'm having problems with the Grape gem and the parameters validation.
The idea behind this is to create a complex entity using nested attributes through an API service.
I have a method to create a trip, trip have many destinations and i want to pass that destinations using a hash (using the accepts_nested_attributes_for helper).
I have this grape restriction over the parameter:
requires :destinations, type: Hash
And I'm trying to send something like this:
{ destinations => [
{ destination: { name => 'dest1'} },
{ destination: { name => 'dest2'} },
{ destination: { name => 'dest3'} }
]}
In order to build something like the structure below inside the method and get the trip created:
{ trip: {
name: 'Trip1', destinations_attributes: [
{ name: 'dest1' },
{ name: 'dest2' },
{ name: 'dest3' }
]
}}
I'm using POSTMAN chrome extension to call the API method.
Here's a screen capture:
If someone can help me i would be very grateful.
By the looks of what you are trying to send, you need to change the Grape restriction, because destinations is an Array, not a Hash:
requires :destinations, type: Array
You don't need the "destination" hash when sending the request:
{ destinations => [
{ name => 'dest1', other_attribute: 'value', etc... },
{ name => 'dest2', other_attribute: 'value', etc... },
{ name => 'dest3', other_attribute: 'value', etc... }
]}
This creates an Array of hashes.
In order to send this through POSTMAN, you'll need to modify that destinations param your sending and add multiple lines in POSTMAN. Something like:
destinations[][name] 'dest1'
destinations[][other_attribute] 'value1'
destinations[][name] 'dest2'
destinations[][other_attribute] 'value2'
destinations[][name] 'dest3'
destinations[][other_attribute] 'value3'
Hope this answers your questions. Let me know if this is what you were looking for.