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!
Related
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,
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.
I am trying to implement a Handlebars helper that will take in some data, and based on that data generate a list. After the list was generated in a helper method, I'd want to loop this list. It will be clear in the example.
The scenario is: I have some data, and as I parse through the data I want to create some custom warnings for inconsistency in that data. The method responsible to generate the warnings from raw JSON data is called generateWarnings().
I have functions to compile template and render the data, defined like this:
function compileTemplate(targetId) {
return Handlebars.compile($("#" + targetId).html());
}
function renderData(template, context, targetId) {
$("#" + targetId).html(template(context));
}
I am calling them:
var warningsPlaceHolderId = "warnings_plholder";
var warningsTemplateId = "warnings_template";
var warningsTemplate = HandlebarsHelpers.compileTemplate(warningsTemplateId);
renderData(warningsTemplate, data, warningsPlaceHolderId);
Handlebars template:
<ol>
{{#each generateWarnings this}}
Warning description: {{this.text}}
{{/each}}
</ol>
Here, generateWarnings helper is supposed to take my raw JSON data, create the actual list of warnings, and display each of this warnings as the element in the HTML list.
I registered the Helper:
Handlebars.registerHelper('generateWarnings', function(data) {
return generateWarnings(data);
});
generateAll: function(data) {
var warning1 = {
type: 'error',
text: 'Testing error!'
};
var warning2 = {
type: 'warning',
text: 'Testing warning!'
};
var warning3 = {
type: 'info',
text: 'Testing info'
};
var arr = [];
arr.push(warning1);
arr.push(warning2);
arr.push(warning3);
return arr;
}
So I would expect the text property of these 3 warnings to be in a list, but instead I am getting an error:
Uncaught TypeError: inverse is not a function at renderData().
Shouldn't this be using generateAll() in your helper?
Handlebars.registerHelper('generateWarnings', function(data) {
return generateWarnings(data); // THIS SHOULD BE generateAll(data)??
});
generateAll: function(data) {
Also, you are using a label?! for your function which won't be callable the way you expect.
This:
generateAll: function(data) { ... };
should be:
var generateAll = function(data ) { ... };
I have never seen a good use for labels in JavaScript.
Your approach seems to be a bit unusual (and overly complicated) as well. Why not just manipulate the data before passing it to your template? I would consider using JavaScript to generate the warnings, then attach them to your data before passing to your template. I don't see a particular reason to be using a Handlebars helper in this situation (granted, I'm not totally familiar with the problem you are trying to solve).
I am trying to upload a list of images. I have the images stored in an array (called images).
I have the previews displayed on the screen.
What I want to do is upload them sequentially, and as they complete their upload, I want to set a flag. When this flag is set (thanks to the power of Knockout), the image disappears from the list.
However, I think due to the async nature of the post command .. I'm not achieving the desired results.
Below is what I am trying to do:
for(var i = 0; i < self.images().length; i++) {
var photo = self.images()[i];
var thisItem = photo;
var object = JSON.stringify({
Image: thisItem.Image,
AlbumID: albumId,
Filesize: thisItem.Filesize,
Filetype: thisItem.Filetype,
Description: thisItem.Description,
UniqueID: thisItem.UniqueID
});
var uri = "/api/Photo/Upload";
$.post({
url: uri,
contentType: "application/json"
}, object).done(function(data) {
if(data.IsSuccess) {
photo.Status(1);
}
}).fail(function() {
// Handle here
}).always(function() {
remainingImages = remainingImages - 1;
if(remainingImages == 0) self.isUploading(false);
});
}
self.isUploading(false);
But I think what's happening is that the for loop ends before all the posts have received a reply. Because online one image is removed.
I tried with a async: false ajax post, but that locked up the screen and then they all disappeared.
I thought the 'done' method would only execute once the post is completed, but I think the whole method just ends once the post commands have been sent, and then I never get the done.
How can I achieve what I'm trying to do... Set each image's status once the post gets a reply?
Your first problem is that you are losing the reference you think you have to each photo object because the loop finishes before your AJAX calls return, so that when they do return, photo is a reference to the last item in self.images().
What we need to do to solve this is to create a new scope for each iteration of the loop and each of those scopes will have its own reference to a particular photo. JavaScript has function scopes, so we can achieve our goal by passing each photo to a function. I will use an Immediately Invoked Function Expression (IIFE) as an example:
for (var i = 0; i < self.images().length; i++) {
var photo = self.images()[i];
(function (thisItem) {
/* Everything else from within your for loop goes here. */
/* Note: the done handler must reference `thisItem`, not `photo`. */
})(photo);
}
Note that you should remove self.isUploading(false); from the last line. This should not be set to false until all of the POST requests have returned.
I have created a functioning fiddle that you can see here.
However, this solution will not perform the POST requests "sequentially". I am not sure why you would want to wait for one POST to return before sending the next as this will only increase the time the user must wait. But for the sake of completeness, I will explain how to do it.
To fire a POST after the previous POST returns you will need to remove the for loop. You will need a way to call the next POST in the always handler of the previous POST. This is a good candidate for a recursive function.
In my solution, I use an index to track which item from images was last POSTed. I recursively call the function to perform the POST on the next item in images until we have POSTed all items in images. The following code replaces the for loop:
(function postNextImage (index) {
var photo = self.images()[i];
var thisItem = photo;
var object = JSON.stringify({
Image: thisItem.Image,
AlbumID: albumId,
Filesize: thisItem.Filesize,
Filetype: thisItem.Filetype,
Description: thisItem.Description,
UniqueID: thisItem.UniqueID
});
var uri = "/api/Photo/Upload";
$.post({
url: uri,
contentType: "application/json"
}, object)
.done(function (data) {
if(data.IsSuccess) {
thisItem.Status(1);
}
})
.fail(function () {
// Handle here
})
.always(function () {
if (index < (self.images().length - 1)) {
index += 1;
postNextImage(index);
} else {
self.isUploading(false);
}
});
})(0);
I have created a fiddle of this solution also, and it can be found here.
I wanted to add a new field to my current system, a last_pwd_modified field, which will store the current timestamp in db whenever user wanted to change their password.
I'm having a problem debugging this for hours since the last_pwd_modified field won't store the current time in db and also a 500 (Internal Server Error) keep occurring. I think it might be the way the last_pwd_modified being pass is wrong, I tried several other ways passing it but keep failing.
Could anyone expert maybe pointed out what seems to be the problem in my code. Really appreciate it and a thank you in advance for your time.
Here is my model
public function changePasswd($uid, $newpassword) {
$conditions=array();
$modelData = array(
'upwd' => sha1(strip_tags($newpassword)),
'uid' => $uid,
'last_pwd_modified' => "CURRENT_TIMESTAMP()"
);
$this->save($modelData);
}
Here is a line in my controller which call the function
$this->users->changePasswd($uid,$ret_pwd);
And here is my viewModel
var changePassword = function () {
var $result = {};
$.ajax({
url: "users/changepwd",
type: "POST",
async: false,
data: $("#changePasswordID").serialize(),
success: function(result) {
$result = $.parseJSON(result);
}
});
}
You need to use UNIX_TIMESTAMP. CURRENT_TIMESTAMP returns date likeY-m-d H:i:s. Use UNIX_TIMESTAMP without argument.
Also, your Database lib could use prepared statement or escaping. Probably you cannot use mysql function as value. I thinkg you could try 'last_pwd_modified' => time().