So I have a component for displaying players which uses my displayPlayerObject function. The problem is it is I do not have the correct route for fetching in the function and for POST in my routes.rb. The model relationships have the players belonging to the teams and the route for getting a player is "http://localhost:3000/api/teams/1/players/1" 1 being the team id for the former and player id in the latter. But how do I make the displayPlayerObject work the same way syntax wise? And how should it look like for the POST in routes.rb? Also I suspect my players controller's 'show' is wrong as well.
displayPlayerObject function (edited):
export const displayPlayerObject = (id, teamId, type) => {
return dispatch => {
const data = { id };
fetch(`/api/teams/:team_id/players/show`, {
method: 'post',
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(data)
})
.then(res => res.json())
.then(responseJSON => { dispatch({ type , player_object: responseJSON})
})
}
};
My routes.rb (edited):
Rails.application.routes.draw do
scope '/api' do
post "teams/show", to: 'teams#show'
post "teams/:team_id/players/show", to: 'players#show'
resources :teams do
resources :players
resources :star_players
end
end
end
Players Controller Show (edited):
def show
Player.find(params[:id])
render json: #player, status: 200
end
Let's make it look better.
First of all Player.find_by(id: params[:id]) is nonsense, since find_by(id: params[:id]) is equal to where(id: params[:id]).take. Better to replace it with classic find(params[:id]) which looks much better.
What about your main question, what is the point to give name for POST with displayObject. For me display something means GET it to me. If you want to get some player you need to call api/teams/:team_id/players/:id. But if you want to create new player you need to make POST request to /api/teams/:team_id/players route.
P.S.1 Just change request method from POST to GET. And then add to your promise this:
.then(res => {
console.log(res)
})
And observe what server returns
P.S.2 Change request to
fetch(`/api/teams/:team_id/players/:id`)
And in controller find player by :id
Related
I'm trying to make an API call from my Vue3 app. The prepared API has an endpoint like http://localhost:8888/api/dtconfigsearch, where one needs to pass a json payload like { "Modelname": "MyFancyModel"} to get the full dataset with the given modelname. Pure get functions without a payload / a body do work from my Vue3 project to the golang backend, but I'm having problems with passing a payload to the backend.
Test with curl -> ok
$ curl -XGET localhost:8888/api/dtconfigsearch -d '{"Modelname" : "MyFancyModel" }'
{"ID":4,"Modelname":"MyFancyModel","ModelId":"96ee6e80-8d4a-b59a-3524-ced3187ce7144000","OutputTopic":"json/fancyoutput"}
$
This is the expected output.
Test with javascript ok
Source file index.js:
const axios = require('axios');
function makeGetRequest() {
axios.get(
'http://localhost:8888/api/dtconfigsearch',
{
data: { Modelname : "MyFancyModel" },
headers: {
'Content-type' : 'application/json'
}
}
)
.then(resp => {
console.log(resp.data)
})
.catch(err => {
console.log(err)
})
}
makeGetRequest()
Output
$ node index.js
{
ID: 4,
Modelname: 'MyFancyModel',
ModelId: '96ee6e80-8d4a-b59a-3524-ced3187ce7144000',
OutputTopic: 'json/fancyoutput'
}
$
Here, I also get the desired output.
Test within Vue fails :-(
Source in the Vue one file component:
onSelection(event) {
let searchPattern = { Modelname : event.target.value }
console.log(event.target.value)
console.log("searchPattern = " + searchPattern)
axios.get("http://localhost:8888/api/dtconfigsearch",
{
data : { Modelname : "Windshield"},
headers: {
'Content-type' : 'application/json',
'Access-Control-Allow-Origin': '*'
}
})
.then(response => {
console.log(response.data)
})
.catch(err => {
console.log(err)
alert("Model with name " + event.target.value + " not found in database")
})
},
Output in browser:
In the image you can see in the terminal log on the right side that the backend is not receiving the body of the API call. However, in the browser information of the call there is content in the config.data part of the object tree, which is the payload / the body. The only thing that bothers me that it is not a json object, but stringified json, although it was entered as json object. According to the documentation, the parameter name (data) in the call should be correct to hold the body content of the api call.
I've tried different header information, looked if it could be a CORS issue, what it isn't to my opinion, exchanged key data with body, used axios instead of axios.get and adapted parameter, all without success. The version of the axios library is 0.27, identical for Vue and vanilla javascript. After checking successfully in javascript, I was sure that it would work the same way in Vue, but it didn't.
Now I'm lost and have no further ideas how to make it work. Maybe someone of you had similar issues and could give me a hint? I'd be very grateful for some tipps!!
I am quite new to AJAX and I am not sure what I am doing wrong. I have a webpage that fetches all comments on a post with an AJAX get request. The issue is that the AJAX request is only successful after the webpage is refreshed. I disabled the cache to see if that would solve the issue, but it didn't.
For example, when I fetch the first comments after refreshing the page from post A and then go onto post B on the website, the comments from the post A appear as the comments for post B, then when I refresh the page the comments for post B are replaced with post B's comments successfully.
I am using jQuery to make the request:
$.ajax({
type: "GET",
url: someURL,
success: (comments) => {
console.log(comments);
comments.questions.forEach(questionComment => {
$('.questionComments').append(
`<div class="comment">
<p>${questionComment.content}</p>
</div>
`
)
});
comments.answers.forEach(answer => {
answer.forEach(answerComment => {
$(`.answer#${answerComment.forId} .answerComments`).append(
`<div class="comment">
<p>${answerComment.content}</p>
</div>
`
)
})
})
},
cache: false
})
Server-Side: (express.js, mongoose)
let allComments = {}
app.get('/questions/:questionID/getComments', (req, res) => {
if (err) return console.error(err)
Comment.find({ forQuestion: true, forId: req.params.questionID }, (err, questionComments) => {
allComments['questions'] = questionComments
})
Answer.find({ questionId: req.params.questionID }, (err, answers) => {
if (err) return console.error(err);
allAnswerComments = []
answers.forEach(answer => {
Comment.find({ forAnswer: true, forId: answer._id }, (err, comments) => {
allAnswerComments.push(comments)
})
});
allComments['answers'] = allAnswerComments
})
res.send(allComments)
})
What the commets object looks like before the reload - a blank object
What the comments object looks like after the reload
When you navigate to a different post / different URL, the object from the previous post / URL is the initial object on the new post, and then when you reload the page the correct object is fetched.
According to your description, the AJAX request works when executed, but the problem is that it's executed only once, at page load. Let's write a function for this purpose:
function sendAJAX() {
$.ajax({
type: "GET",
url: someURL,
success: (comments) => {
console.log(comments);
comments.questions.forEach(questionComment => {
$('.questionComments').append(
`<div class="comment">
<p>${questionComment.content}</p>
</div>
`
)
});
comments.answers.forEach(answer => {
answer.forEach(answerComment => {
$(`.answer#${answerComment.forId} .answerComments`).append(
`<div class="comment">
<p>${answerComment.content}</p>
</div>
`
)
})
})
},
cache: false
})
}
And ensure that it's called periodically:
sendAJAX();
seInterval(sendAJAX, 10000);
This will still be incorrect, because it will add all comments every ten seconds. To improve this, you could add a value representing the moment of the last request and on server-side load only the comments that were created between that moment and the current one.
Can you please describe your working on get parameters how are you setting it.Is there any cookie involved there? Telling the get comment will definitely help us to figure our the problem how ever sendAjax() function will be great idea instead of writing ajax in open Script.
On other side I will never recommend you seInterval(sendAJAX, 10000); instead install a service worker and hit it back from your API when new comment is added. And then it should only fetch the last one and put it in your comment section.
As per your description it seems that the main problem is comment of Post A are appearing in comment of Post B.
Now, there is minimal data provided and by looking and your code, I am assuming that you are not refreshing page programmatically when switching from Post A to Post B.
So, the root cause of your problem is append. You are appending data to your div's. So, before your foreach loops, first clear the html using
$('.questionComments').html("")
And for the answers replace it as below
comments.answers.forEach(answer => {
let first = true;
answer.forEach(answerComment => {
if(first) {
$(`.answer#${answerComment.forId} .answerComments`).html("")
first = false;
}
$(`.answer#${answerComment.forId} .answerComments`).append(
`<div class="comment">
<p>${answerComment.content}</p>
</div>
`
)
})
})
The above snippet is just to demonstrate that you have to identify when the first comment is added and then reset it.
I haven't tested the snippet but I hope you got an idea what is wrong in the code.
I am using react-adminframework, and I have written my own DataProvider. I am trying to accomplish that when an User is created, an instance of UserPossession is created as well. My code bellow accomplishes that, but react-admin Front-end just displays the warning message:
Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body
I checked the Network tab in Developer Tools and every request to server is correct, there is no error. Which leaves me confused and stuck with this, because I have no idea what that warning means or why is it even occuring.
My code is a part of convertDataRequestToHTTP constant and looks like this:
if (resource === 'User') {
url = `${apiUrl}/${resource}`;
options.body = params.data;
httpClient(url, {
method: 'POST',
body: JSON.stringify(options.body),
})
.then(response => (
url = `${apiUrl}/Location`,
options.method = 'POST',
options.body = JSON.stringify({
"odata.type": "HardwareDatabase.UserPossession",
"Name": response.json.Login,
"UserId": response.json.Id
}),
httpClient(url, {
method: options.method,
body: options.body
})
));
}
If you have any questions regarding the code I can clarify.
Thank you for any ideas in advance.
Since you are stating that this code snippet is a part of convertDataRequestToHTTP I might see the issue. httpClient cannot be used in this constant since it creates duplicit calls to API or in your case, this Warning. Correct way would be to only state the options constant.
url = `${apiUrl}/${resource}`;
options.body = JSON.stringifiy(params.data);
options.method = 'POST';
Later in the constant that converts response from OData to mandatory React Admin format, state the httpClient.
params.data = {
"odata.type": "HardwareDatabase.UserPossession",
"Name": response.json.Login,
"UserId": response.json.Id
};
httpClient(`${apiUrl}/Location`, {
method: 'POST',
body: JSON.stringify(params.data),
})
Unfortunately, the GET method for XMLHttpRequest and fetch don't support request bodies.
A temporary work around I found was to use the axios library, which does let you send GET with request bodies.
const res = await axios.get("/api/devices", {
data: { deviceName: 'name' }
})
I haven't been able to figure out how to get my JavaScript to send a request in a format that Rails will accept when I try to edit a Game with a File parameter and an array parameter in the same payload.
The Rails controller looks like this (simplified, obviously):
class GamesController < ApplicationController
def update
#game = Game.find(params[:id])
authorize #game
respond_to do |format|
if #game.update(game_params)
format.html { render html: #game, success: "#{#game.name} was successfully updated." }
format.json { render json: #game, status: :success, location: #game }
else
format.html do
flash.now[:error] = "Unable to update game."
render :edit
end
format.json { render json: #game.errors, status: :unprocessable_entity }
end
end
end
private
def game_params
params.require(:game).permit(
:name,
:cover,
genre_ids: [],
engine_ids: []
)
end
end
So I have JavaScript like so:
// this.game.genres and this.game.engines come from
// elsewhere, they're both arrays of objects. These two
// lines turn them into an array of integers representing
// their IDs.
let genre_ids = Array.from(this.game.genres, genre => genre.id);
let engine_ids = Array.from(this.game.engines, engine => engine.id);
let submittableData = new FormData();
submittableData.append('game[name]', this.game.name);
submittableData.append('game[genre_ids]', genre_ids);
submittableData.append('game[engine_ids]', engine_ids);
if (this.game.cover) {
// this.game.cover is a File object
submittableData.append('game[cover]', this.game.cover, this.game.cover.name);
}
fetch("/games/4", {
method: 'PUT',
body: submittableData,
headers: {
'X-CSRF-Token': Rails.csrfToken()
},
credentials: 'same-origin'
}).then(
// success/error handling here
)
The JavaScript runs when I hit the submit button in a form, and is supposed to convert the data into a format Rails' backend will accept. Unfortunately, I'm having trouble getting it to work.
I'm able to use JSON.stringify() instead of FormData for submitting the data in the case where there's no image file to submit, like so:
fetch("/games/4", {
method: 'PUT',
body: JSON.stringify({ game: {
name: this.game.name,
genre_ids: genre_ids,
engine_ids: engine_ids
}}),
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': Rails.csrfToken()
},
credentials: 'same-origin'
})
This works fine. But I haven't been able to figure out how to use JSON.stringify when submitting a File object. Alternatively, I can use a FormData object, which works for simple values, e.g. name, as well as File objects, but not for array values like an array of IDs.
A successful form submit with just the ID arrays (using JSON.stringify) looks like this in the Rails console:
Parameters: {"game"=>{"name"=>"Pokémon Ruby", "engine_ids"=>[], "genre_ids"=>[13]}, "id"=>"4"}
However, my current code ends up with something more like this:
Parameters: {"game"=>{"name"=>"Pokémon Ruby", "genre_ids"=>"18,2,15", "engine_ids"=>"4,2,10"}, "id"=>"4"}
Unpermitted parameters: :genre_ids, :engine_ids
Or, if you also upload a file in the process:
Parameters: {"game"=>{"name"=>"Pokémon Ruby", "genre_ids"=>"13,3", "engine_ids"=>"5", "cover"=>#<ActionDispatch::Http::UploadedFile:0x00007f9a45d11f78 #tempfile=#<Tempfile:/var/folders/2n/6l8d3x457wq9m5fpry0dltb40000gn/T/RackMultipart20190217-31684-1qmtpx2.png>, #original_filename="Screen Shot 2019-01-27 at 5.26.23 PM.png", #content_type="image/png", #headers="Content-Disposition: form-data; name=\"game[cover]\"; filename=\"Screen Shot 2019-01-27 at 5.26.23 PM.png\"\r\nContent-Type: image/png\r\n">}, "id"=>"4"}
Unpermitted parameters: :genre_ids, :engine_ids
TL;DR: My question is, how can I send this payload (a name string, an array of IDs, as well as a game cover image) to Rails using JavaScript? What format will actually be accepted and how do I make that happen?
The Rails app is open source if that'd help at all, you can see the repo here. The specific files mentioned are app/controllers/games_controller.rb and app/javascript/src/components/game-form.vue, though I've simplified both significantly for this question.
I figured out that I can do this using ActiveStorage's Direct Upload feature.
In my JavaScript:
// Import DirectUpload from ActiveStorage somewhere above here.
onChange(file) {
this.uploadFile(file);
},
uploadFile(file) {
const url = "/rails/active_storage/direct_uploads";
const upload = new DirectUpload(file, url);
upload.create((error, blob) => {
if (error) {
// TODO: Handle this error.
console.log(error);
} else {
this.game.coverBlob = blob.signed_id;
}
})
},
onSubmit() {
let genre_ids = Array.from(this.game.genres, genre => genre.id);
let engine_ids = Array.from(this.game.engines, engine => engine.id);
let submittableData = { game: {
name: this.game.name,
genre_ids: genre_ids,
engine_ids: engine_ids
}};
if (this.game.coverBlob) {
submittableData['game']['cover'] = this.game.coverBlob;
}
fetch(this.submitPath, {
method: this.create ? 'POST' : 'PUT',
body: JSON.stringify(submittableData),
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': Rails.csrfToken()
},
credentials: 'same-origin'
})
}
I then figured out that, with the way DirectUpload works, I can just send the coverBlob variable to the Rails application, so it'll just be a string. Super easy.
You can convert the File object to a data URL and include that string in JSON, see processFiles function at Upload multiple image using AJAX, PHP and jQuery or use JSON.stringify() on the JavaScript Array and set that as value of FormData object, instead of passing the Array as value to FormData.
submittableData.append('game[name]', JSON.stringify(this.game.name));
submittableData.append('game[genre_ids]', JSON.stringify(genre_ids));
submittableData.append('game[engine_ids]', JSON.stringify(engine_ids));
seems a simple problem but, i don't know why this is causing a little stress, anyway.
here is situation:
after get the attachments on the Post.show in rails
async getAttachments() {
// this.setState({showProgress: true})
let auth_token = window.localStorage.getItem("auth_token");
let post_id = this.props.match.params.id;
fetch(`/posts/${post_id}/attachments`, {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Access: auth_token
}
})
.then(response => response.json())
.then(json => {
this.setState({
attachments: json.attachments,
image: json.attachments.image,
thumb: json.attachments.image.thumb,
url: json.attachments.thumb.url
});
})
.catch(error => {
console.error(error);
});
}
i decide to render it as usual
with {this.state.attachments}
but did not rendered.
so i tried to map and then i tried
var attachments = this.state.attachments.map(a => {
return (
<div key={a.id}>
<img source={a.thumb} />
<img source={a.url}>{a.url}</img>
</div>
);
});
the thing is ever rails carrierwave attachment/files. create a object inside the array and many people still have doubt how to grab and render these files.
The HTML image tag uses an attribute named src, not source. That's your problem.
Aside: Consider this snippet of your code:
this.setState({
attachments: json.attachments, // OK, keep
image: json.attachments.image, // bad path into the object
thumb: json.attachments.image.thumb, // bad path into the object
url: json.attachments.thumb.url // bad path into the object
});
The three lines starting with image, thumb, and attachment should be deleted.
The result of using the code without deleting those lines will be that your state looks like:
{
attachments: <an array of attachments>, // correct, wanted, should keep
image: undefined,
thumb: undefined,
url: undefined
}
This is because json.attachments is an array so it does not have any data at the paths you are calling. By "path into the object" I merely mean a sequence of keys to call in sequence of dot notation.