I've got an AngularJS App using Restangular for persistence on top of a Rails 4 API. I've run into a problem with my updates not working.
My code in the routeProvider:
when('/course/edit/:courseId', {
controller: 'CourseEdit',
templateUrl: '/assets/templates/course/form.html',
resolve: {
course: function(Restangular, $route) {
return Restangular.one('courses', $route.current.params.courseId).get();
}
}
})
My controller function:
function CourseEdit($scope, $location, course) {
$scope.course = course;
$scope.save = function() {
$scope.course.put().then(function() {
$location.path('/course/view/' + $scope.course.id);
});
};
}
The request for the PUT is being made to http://mysite.com/courses rather than http://mysite.com/courses/id
like I expected it to be, and I can't seem to figure out why.
The error that rails throws out is 'No route matches [PUT] "/courses".
Any help would be greatly appreciated.
I managed to figure it out by poking around at my set up a bit more. I had copied some config for Restangular from a tutorial that I'd found, without really examining whether or not all pieces were necessary.
In my config()
RestangularProvider.setRestangularFields({
id: '_id.$oid'
});
That doesn't match what my fields are in my API, and was the culprit for the problems that I was having. I ended up removing that, leaving only the config for setBaseUrl and it started working again.
Hopefully this will help someone else out who's struggling from the same blindness that I was.
Related
I just started learning MeteorJS and after completing the tutorial, I decided to play around with the Twitter API. Initially, I followed this tutorial
http://artsdigital.co/exploring-twitter-api-meteor-js/
Once completing that, what I wanted to do is scrape data from a tweet and display it on the client side.
N/A = proper authentication
Here's the code I've written:
if (Meteor.isClient) {
Session.setDefault('screen_name', 'John');
Template.hello.helpers({
screen_name: function () {
return Session.get('screen_name');
}
});
Template.hello.events({
'click button': function () {
T.get('search/tweets',
{
q: '#UCLA',
count: 1
},
function(err,data,response) {
var user_name = data.statuses[0].users.screen_name;
Session.set('screen_name', user_name);
}
)
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
var Twit = Meteor.npmRequire('twit');
var T = new Twit({
consumer_key: 'N/A', // API key
consumer_secret: 'N/A', // API secret
access_token: 'N/A',
access_token_secret: 'N/A'
});
});
}
What I believe the problem is that, the 'click button' function, the 'T' is seen to be undefined so the compiler doesn't know what that is or where it came. That thought did spark a thought in my mind to move what I have written inside the
if (Meteor.isServer) to if (Meteor.isClient)
But to no avail. It didn't work. What my reasoning is that once Meteor starts, the server starts, so if the server declares the variable T, shouldn't we be able to access it on the client side too?
I'm not sure if my approach is correct/don't know the conventions of Meteor/Meteor NOOB..so if someone could please help me, that will be highly appreciated!
Thanks!
You put a "var" declaration in front of your "T" variable. This binds the scope to the server side context of the app. I bet if you got rid of the var and made "T" global, then you would be able to access it from the client side as well.
I'm new to Angular. I've tried everything I know how and Google searches have surprisingly few tutorials on this particular question. Here's the last code I tried:
index.html
<form ng-submit="addArticle(articles)">
<input type="text" id="title" ng-model="newPost.title">
<input type="text" id="body" ng-model="newPost.body">
<input type="submit" value="Submit">
</form>
articles controller
app.controller('ArticlesCtrl', function($scope, Article) {
$scope.articles = Article.query();
$scope.newPost = Article.save();
});
articles service (rails backend)
app.factory('Article', function($resource) {
return $resource('http://localhost:3000/articles');
});
I can retrieve data just fine. But I can't submit any new data to the rails backend. On page load, the rails server error is:
Started POST "/articles" for 127.0.0.1 at 2015-02-08 18:26:29 -0800
Processing by ArticlesController#create as HTML
Completed 400 Bad Request in 0ms
ActionController::ParameterMissing (param is missing or the value is empty: article):
app/controllers/articles_controller.rb:57:in `article_params'
app/controllers/articles_controller.rb:21:in `create'
Pressing the submit button does nothing at all. The form basically does not work and the page is looking for a submission as soon as it loads.
I understand what the error says, that it's not receiving the parameters from the form. What I don't understand is what that should look like in my controller and/or form.
What am I doing wrong and how do I fix this?
Angular has a feature called services which acts as a model for the application. It's where I'm communicating with my Rails backend:
services/article.js
app.factory('Article', function($resource) {
return $resource('http://localhost:3000/articles/:id', { id: '#id'},
{
'update': { method: 'PUT'}
});
});
Even though the :id is specified on the end, it works just as well for going straight to the /articles path. The id will only be used where provided.
The rest of the work goes into the controller:
controllers/articles.js
app.controller('NewPostCtrl', function($scope, Article) {
$scope.newPost = new Article();
$scope.save = function() {
Article.save({ article: $scope.article }, function() {
// Optional function. Clear html form, redirect or whatever.
});
};
});
Originally, I assumed that the save() function that's made available through $resources was somewhat automatic. It is, but I was using it wrong. The default save() function can take up to four parameters, but only appears to require the data being passed to the database. Here, it knows to send a POST request to my backend.
views/articles/index.html
<form name="form" ng-submit="save()">
<input type="text" id="title" ng-model="article.title">
<input type="text" id="body" ng-model="article.body">
<input type="submit" value="Submit">
</form>
After getting the service setup properly, the rest was easy. In the controller, it's required to create a new instance of the resource (in this case, a new article). I created a new $scope variable that contains the function which invokes the save method I created in the service.
Keep in mind that the methods created in the service can be named whatever you want. The importance of them is the type of HTTP request being sent. This is especially true for any RESTful app, as the route for GET requests is the same as for POST requests.
Below is the first solution I found. Thanks again for the responses. They were helpful in my experiments to learn how this worked!
Original Solution:
I finally fixed it, so I'll post my particular solution. However, I only went this route through lack of information how to execute this through an angular service. Ideally, a service would handle this kind of http request. Also note that when using $resource in services, it comes with a few functions one of which is save(). However, this also didn't work out for me.
Info on $http: https://docs.angularjs.org/api/ng/service/$http
Info on $resource: https://docs.angularjs.org/api/ngResource/service/$resource
Tutorial on Services and Factories (highly useful): http://viralpatel.net/blogs/angularjs-service-factory-tutorial/
articles.js controller
app.controller('FormCtrl', function($scope, $http) {
$scope.addPost = function() {
$scope.article = {
'article': {
'title' : $scope.article.title,
'body' : $scope.article.body
}
};
// Why can't I use Article.save() method from $resource?
$http({
method: 'POST',
url: 'http://localhost:3000/articles',
data: $scope.article
});
};
});
Since Rails is the backend, sending a POST request to the /articles path invokes the #create method. This was a simpler solution for me to understand than what I was trying before.
To understand using services: the $resource gives you access to the save() function. However, I still haven't demystified how to use it in this scenario. I went with $http because it's function was clear.
Sean Hill has a recommendation which is the second time I've seen today. It may be helpful to anyone else wrestling with this issue. If I come across a solution which uses services, I'll update this.
Thank you all for your help.
I've worked a lot with Angular and Rails, and I highly recommend using AngularJS Rails Resource. It makes working with a Rails backend just that much easier.
https://github.com/FineLinePrototyping/angularjs-rails-resource
You will need to specify this module in your app's dependencies and then you'll need to change your factory to look like this:
app.factory('Article', function(railsResourceFactory) {
return railsResourceFactory({url: '/articles', name: 'article');
});
Basically, based on the error that you are getting, what is happening is that your resource is not creating the correct article parameter. AngularJS Rails Resource does that for you, and it also takes care of other Rails-specific behavior.
Additionally, $scope.newPost should not be Article.save(). You should initialize it with a new resource new Article() instead.
Until your input fields are blank, no value is stored in model and you POST empty article object. You can fix it by creating client side validation or set default empty string value on needed fields before save.
First of all you should create new Article object in scope variable then pass newPost by params or access directly $scope.newPost in addArticle fn:
app.controller('ArticlesCtrl', function($scope, Article) {
$scope.articles = Article.query();
$scope.newPost = new Article();
$scope.addArticle = function(newPost) {
if (newPost.title == null) {
newPost.title = '';
}
// or if you have underscore or lodash:
// lodash.defaults(newPost, { title: '' });
Article.save(newPost);
};
});
If you want use CRUD operations you should setup resources like below:
$resource('/articles/:id.json', { id: '#id' }, {
update: {
method: 'PUT'
}
});
I have a Meteor app which uses a router as per the sample Todos app, eg.:
var TodosRouter = Backbone.Router.extend({
routes: {
":list_id": "main"
},
main: function (list_id) {
...
},
...
});
Router = new TodosRouter;
I have an external service that lets me know when it is finished via a POST request to a URL that I specify. On receiving the POST request, it needs to perform some functions. Can I set this up consistent with the above framework? If not, am I better off writing a separate non-Meteor (eg. nodejs) app to handle the POSTs, or is there a way to make it work within Meteor?
I know this, this and this StackOverflow question are similar, but none of the answers explains how to do it with the Backbone.Router.
Thanks!
Here's how I migrated from the Backbone.Router to Iron-Router. I couldn't see a way to use them both at once.
First, I put code like in #Christian Fritz's answer, in the server folder:
Router.map(function () {
this.route('foo', {
path: '/foo',
where: 'server',
action: function() {
if (this.request.method == 'POST') {
console.log('post request received');
} else {
this.response.writeHead(404);
}
}
})
});
That handles the POST request, as tested by including a console.log in the action (which will write to the terminal window where meteor is running), and typing:
curl --data "p=bar" localhost:3000/foo
Then I replaced the Backbone.Router call with code in the client folder, as described in the Iron-Router docs, eg.
Router.configure({
layoutTemplate: 'layout'
});
Router.map(function () {
this.route('home', {
path: '/',
template: 'homepage',
});
});
Then I had to rewrite my templates so that pages were generated from different templates, instead of from different Session variable settings. This actually simplified my code, eg. instead of:
<body>
{{#if onPage 'home'}}
{{> homepage }}
{{/if}}
{{#if onPage 'help'}}
{{> helppage }}
{{/if}}
...
</body>
and
UI.registerHelper("onPage", function(name) {
return Session.equals('page', name);
});
it's just:
<template name="layout">
{{>yield}}
</template>
Any javascript that needs to direct the user to a specific page uses Router.go() with the route name:
Router.go('home');
Beyond that, I found a few places where I used to be able to assume my collections .findOne() had found one, but seem to have broken with Iron-Router. Wrapping these in ifs fixed them.
In Meteor 0.8, to check what page you are on you can use (see here):
Router.current().lookupProperty('template')
I am really scratching my head over this one. I am using cloudant as my couchdb provider and attempting to save a document to my db. I first establish what my db is in the following code.
db = jQuery.couch.db("https://fullscore.cloudant.com/fullscore");
I do a console.log and see that the URI and name are correct in the object.
db.saveDoc(doc, {
success: function(data) {
console.log("posted");
},
error: function(status) {
console.log("failure");
}
});
However when I look at the post, it posts to: https://cloudant.com/https%3A%2F%2Ffullscore.cloudant.com%2Ffullscore/
Which as you can see is totally wrong. Obviously there is something built in to append onto cloudant.com ... anyone know how this can be overridden? Or perhaps its some other problem? I'm getting a 404 error.
--Ashley
To clarify the exact code, you need to set the URL prefix on your couch object, then use just the db name:
$.couch.urlPrefix = "https://username.cloudant.com";
var db = $.couch.db("dbname");
I'm guessing you're passing the full database URL in a place where you're only supposed to pass the database name (i.e. the latter "fullscore" part in https://fullscore.cloudant.com/fullscore"). You've probably already set up the host name at some earlier point in your program.
I am facing a problem with getting a response in fetch function of backbone.js. I am using Backbone.js with rails.
$(document).ready(function(){
window.projectPerformanceReport.fetch({
success: function(e) {
$("#loading").hide();
window.questionListView = new QuestionListView;
window.questionListView.render();
window.headerView = new HeaderView;
window.headerView.render();
},
error: function() {
alert("Error");
}
});
});
There is already a URL set for the fetch method and it does make a request to online server which returns a JSON object. I checked by hitting that request independently in new tab and it worked fine. Also the JSON object returned is valid. But when i run this page i always get popup of 'Error'?
Any help would be great.
Thanks
Okay
I got what was the issue..... The thing that was happening was, The Java_Servlet which i was calling to get the JSON object was returning the data in text format instead of JSON... (But it was a valid JSON string)... and while calling the ajax method i had set the type as 'json' so it was failing.
Thanks to everyone who read my question and thought for a solution.
But one strange thing which i observed was, same code was working in production and not in my local machine??? Does anyone have some idea on why it could be behaving like that..???
Thanks :) Now i can sleep properly!!