Calling Rails controller data through JS AJAX call - javascript

I'm making a rails app using this code in the controller to call an API- I initially call the inventories endpoint, then make separate calls to two other id endpoints store_id, product_id to grabs specifics pieces of data linked to the inventories. This data gets passed into a hash that becomes '#inventories / transformed results':
class InventoriesController < ApplicationController
def index
response = Typhoeus.get("http://lcboapi.com/inventories")
parsed_json = JSON.parse(response.body)
transformed_results = []
parsed_json["result"].each do |inventory|
transformed_results.push(
{
product_name: product_lookup(inventory["product_id"]),
store_name: store_lookup(inventory["store_id"]),
quantity: inventory["quantity"],
}
)
end
#inventories = transformed_results
end
private
def store_lookup(store_id)
response = Typhoeus.get("http://lcboapi.com/stores/#{store_id}")
parsed_json = JSON.parse(response.body)
return parsed_json["result"]["name"]
end
def product_lookup(product_id)
response = Typhoeus.get("http://lcboapi.com/products/#{product_id}")
parsed_json = JSON.parse(response.body)
return parsed_json["result"]["name"]
end
end
My question is how best to get my json hash through AJAX into a form I can pass through and iterate in assets/javascript.
I am aware I can build it into the view (html.erb) and have done so, but I want to make my data interact with DOM elements.
Note: I've tried doing a simple console log to show the json data in the console as a test, with no response. I'm okay with using jQuery until I get comfortable with React, but I'm not sure how to grab my #inventories data from 'assets/javascript/inventories.js' - for instance, if I wanted to grab something from a csv data bases I'd use the following, but in this case it's not quite there:
document.addEventListener('DOMContentLoaded', function () {
console.log("ready!");
$.ajax({
url: "/inventories",
method: "GET"
}).done(function(data){
var products = []
data.forEach(function(item){
products.push(item.product_name).toString();
console.log(products);
});
});
})

In one of your js files (in assets/javascript), you'll need to do something like:
storeLookupResults = $.ajax({
url: "/inventories.js",
type: 'GET'
})
storeLookupResults.success (data) =>
# do stuff with your results
# 'data' will contain your json
NOTE: I made up the route, so you'll need to make sure you use a real route
Then, in your InventoriesController, modify index to something like:
def index
response = Typhoeus.get("http://lcboapi.com/inventories")
parsed_json = JSON.parse(response.body).with_indifferent_access
#inventories = parsed_json[:result].map do |inventory|
{
product_name: product_lookup(inventory[:product_id]),
store_name: store_lookup(inventory[:store_id]),
quantity: inventory[:quantity],
}
end
respond_to do |format|
format.html
format.json {render json: #inventories, status: :ok}
end
end
Note that .map returns an array. So, you don't have to do:
transformed_results = []
parsed_json["result"].each do |inventory|
transformed_results.push(
{
product_name: product_lookup(inventory["product_id"]),
store_name: store_lookup(inventory["store_id"]),
quantity: inventory["quantity"],
}
)
end
#inventories = transformed_results
Also note that I did:
parsed_json = JSON.parse(response.body).with_indifferent_access
It's a purely stylistic preference. I like using symbols instead of strings.

Related

With JS fetch-PUT i update my db (and it does indeed) but with fetch-GET it displays the data just before the update

Im working on a Django project kind of network. I have a JS code in which with a fetch-PUT i update my db and i can check from the file that it is updated.
function update_like(identity) {
fetch('/like',{
method: 'PUT',
body: JSON.stringify({
id: identity,
})
},
show(identity)
)
};
And then with a fetch-GET i try to retrieve the data
function show(identity) {
fetch('/like', {
headers: {
'Cache-Control': 'no-cache'
}
})
.then(response => response.json())
.then(likedic => {
console.log(likedic);
if (likedic[identity]){
document.getElementById(`arithmos${identity}`).innerHTML = ` ${likedic[identity].length}`;
} else {
document.getElementById(`arithmos${identity}`).innerHTML =' 0';
}
});
}
the thing is, that every time, it displays the data from the just previous updated db.
I mean first time i run update_like function, the show function diplays the db as it was before update_like function runs. But i can see from the file that db is updated.
Second time i run update_like function the show function diplays the db as it should be the first time, even if i can see again from the file that db is updated etc.
I suppose that it doesn't have enough time to read the update db. I have tryied so many things but i cant make it work. Underneath is my python function
def like(request):
likedic = {}
if request.method == 'GET':
allcomments = Like.objects.all()
for i in range(len(allcomments)):
if allcomments[i].comment.id not in likedic.keys():
likedic[allcomments[i].comment.id] = []
likedic[allcomments[i].comment.id].append(allcomments[i].user.username)
print('likedic',likedic)
else:
likedic[allcomments[i].comment.id].append(allcomments[i].user.username)
return JsonResponse(likedic, safe=False)
elif request.method == "PUT":
data = json.loads(request.body)
Likes = Like.objects.filter(comment = Comment.objects.get(id = data['id']), user = User.objects.get(username = request.user.username))
if Likes:
Likes.delete()
else:
Likes = Like(comment = Comment.objects.get(id = data['id']), user = User.objects.get(username = request.user.username))
Likes.save()
return HttpResponse(status=204)
# Email must be via GET or PUT
else:
return JsonResponse({
"error": "GET or PUT request required."
}, status=400)
I would really apreciate some advise. Thanks so much in advance.
Use async functions instead of regular functions, and await the completion of the first function before performing the 2nd. For example, convert update_like to an async funciton.
Later, call show only after awaiting update_like:
// `await` must be used within an async fucntion
// So, I'll use await inside this immediately invoked function:
(async function()
{
let identity = "someIdentity";
await update_like(identity); // Waits until update is complete
return show(identity);
})();
I finally found a solution! I added a setTimeout function before I call show function. I set 0.05sec and it works! I dont know if this is the right or the best way to do it but it finally works!
function update_like(identity) {
fetch('/like',{
method: 'PUT',
body: JSON.stringify({
id: identity,
})
},
setTimeout(() => {
show(identity);
}, 50)
)
};

Field 'id' expected a number but got <SimpleLazyObject: <User: username>>

I have been stuck on this error for some time and I can't wrap my head around the problem or what it even means.
I found some answers but none really solved my issue.
Here is a brief desciption of what I do:
In Javascript, I call this function with an int as a parameter such as:
function follow (user_id) {
// retrieves post for current selection
fetch(`/follow/${user_id}`, {
method: 'PUT',
body: JSON.stringify({
follow: true
})
})
}
My url path, from url.py, is as follow:
path('follow/<int:user_id>', views.follow, name="follow")
finally, in views.py, this function is called:
def follow(request, user_id):
user = User.objects.get(id = user_id)
if request.method == "PUT":
data = json.loads(request.body)
if data.get("follow") is not None:
followed = Followed(
user_id = user.id,
followed_by_id = request.user,
)
followed.save()
return HttpResponseRedirect(reverse("index"))
else:
return HttpResponseRedirect(reverse("index"))
I have tried a few different approaches such as removing the .value but I keep getting the following error:
Field 'id' expected a number but got <SimpleLazyObject: <User: username>>
I check along the way and the ID is an int all the way until it is passed to the model to be saved.
I am using the abstractUser model.
Let me know if more information is needed.
Kind regards,

How to pass data between pageFunction executions in Apify web

I'm scraping website with Apify. I want to scrape different types of pages and then combine the data into one data set. Now i have different sets of data for each kind of pages (users, shots). How to transfer data between pageFunction executions, ex. to calculate followers number for each shot author.
async function pageFunction(context) {
const { request, log, jQuery } = context;
const $ = jQuery;
if (request.url.indexOf('/shots/') > 0) {
const title = $('.shot-title').text();
return {
url: request.url,
title
};
} else if (request.userData.label === "USER") {
var followers_count = $('.followers .count').first().text();
return {
url: request.url,
followers_count
};
}
}
If I understand the question correctly, you can pass the data through crawled pages and save only one item in the end. For this use case, you can work with userData, which you can pass with every request.
For example, if you would like to pass the data from /shots site to the USER, you could do it like this. (but it requires you to enqueue pages manually to control the flow of the data, also this approach except that the /shots type of the page is the first one you visit and then continue)
async function pageFunction(context) {
const { request, log, jQuery } = context;
const $ = jQuery;
if (request.url.indexOf('/shots/') > 0) {
const title = $('.shot-title').text();
const userLink = 'some valid url to user page'
//add to the queue your request with the title in the userData
await context.enqueueRequest({
url: userLink,
userData:{
label:'USER',
shotsTitle: title
}
})
} else if (request.userData.label === "USER") {
var followers_count = $('.followers .count').first().text();
//here you need to get the shotsTitle and return it
return {
url: request.url,
followers_count,
shotsTitle: request.userData.shotsTitle
};
}
}
If you would need to share the between runs of the actors, that is other topic, let me know if it helped.
Also would recommend going through the getting started guide which is here.

Add attributes to RoR object dynamically with Ajax

I would like to dynamically add attributes to a Ruby on Rails object so I can access them with an Ajax call. I understand that I can send the info with another Ajax call, but I would much prefer to add the :first_name and :avatar_url attributes dynamically. Here is my code...
def get_info
comments = []
allTranslations.each do |trans|
if trans.comments.exists?
trans.comments.each do |transComment|
user = ...
class << transComment
attr_accessor :first_name
attr_accessor :avatar_url
end
transComment.first_name = user.first_name
transComment.avatar_url = user.avatar.url
comments.push(transComment)
puts("trans user comments info")
transComments.each do |x|
puts x['comment']
puts x['first_name']
puts x.first_name
puts x['avatar_url']
end
end
end
end
#ajaxInfo = {
translationUsers: allTranslations,
currentUserId: #current_user.id,
transComments: transComments
}
render json: #ajaxInfo
end
Out of the 4 print statements, only puts x.first_name prints, and none of the attributes are added to the objects when I log the results on my console.
Here is the corresponding Javascript and Ajax:
$('.my-translations').click(function(){
$('#translation').empty();
getTranslations(id).done(function(data){
console.log(data)
var transUsers = []
...
});
});
function getTranslations(translationId) {
return $.ajax({
type: "GET",
url: '/get_translations_users',
data: {
translationId: translationId
},
success: function(result) {
return result;
},
error: function(err) {
console.log(err);
}
});
};
Any tips or advice is appreciated! Thanks :)
Out of the 4 print statements, only puts x.first_name prints
This is because when you call x['comment'] etc you call the [] method on x object and I don't think that the object is an hash. when you call .first_name you use the new attr_accessor created dynamically; i think also . avatar_url should work.
Does it work if you do instead:
#ajaxInfo = {
translationUsers: allTranslations,
currentUserId: #current_user.id,
transComments: comments
}
I found this awesome thread that answers my questions: How to add new attribute to ActiveRecord
As stated by #CuriousMind attr_accessor creates properties, not hashes.
I solved this issue by following the solution by #Chris Kerlin
Thanks!

Backbone collection not updating with JSONP request

I just recently started using Backbone.js and I'm working on an app now using Brunch that does a JSONP request to an external API to populate my collection and models. I'm following these previous posts (this and this) on doing JSONP requests with Backbone, but my collection still isn't getting the data for some reason.
My model (app/models/model.js):
module.exports = Backbone.Model.extend({
});
My collection (app/models/collection.js):
var Post = require('./model');
module.exports = Backbone.Collection.extend({
model: Post,
url: "http://somedata.com/api/posts/list/stuff",
sync: function(method, model, options) {
options.timeout = 10000;
options.dataType = "jsonp";
options.jsonp = "JSONPcallback";
return Backbone.sync(method, model, options);
},
parse: function(response) {
if (response) {
var parsed = [];
for(var i = 0; i < response.results.length; i++) {
parsed.push(response.results[i][0]);
}
return parsed;
}
}
});
Then, in the initialize method in app/application.js I'm calling it by:
var Category = require('models/collection');
this.cat = new Category();
this.cat.fetch();
Now, when I look at the parse function in console.log, I see the data being fetched, so the request is going through successfully. However, when my views are rendered and I do console.log(application.cat.models) in app/views/view.js, I get nothing -- why's this happening? Is there anything wrong with the code on my model/collection?
Also, the JSONP data has the following format, which is why looping through for response.results[i][0] and returning an array with all of it, that should do the trick, right?
{"results":[
{"0":{"id":xxx,"title":xxx,"link":xxx},
"description":xxx},
{"0":{"id":xxx,"title":xxx,"link":xxx},
"description":xxx},
{"0":{"id":xxx,"title":xxx,"link":xxx},
"description":xxx},...
]}
Would really appreciate any help...
I have 2 comments here :
I see that you have names both your model and collection as module.exports , a common practice is to make the model as singular (module.export) and make the collection for those models plural module.exports , just common practice , nothing "wrong" otherwise
You can have 2 callbacks in your code , when the collection is done fetching data(asynchronous event) also considering module.exports as your collection here ,
A. You could do this :
module.exports.fetch({
success : function(data){
console.log(JSON.stringiy(data));
//do remaining programming here
}
});
B. you could have a event listener for reset , from the documentation here , the collection fires a reset event when it completes the fetch , so could add an event listener on the collection like this :
module.exports.on('reset',function(data){
console.log(JSON.stringify(data));
//do remaining programming here
},this);

Categories