Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Handle API-style requests #383

Closed
wants to merge 3 commits into from

3 participants

@ben-ng
Owner

These changes allow Geddy to behave more like an API when dealing with certain content types. I added two helper methods in base_controller so that our scaffolding doesn't get too bulky.

You still need to make some minor changes to Backbone (howdy, #382) in order to get Geddy to do content negotiation correctly. See this gist for an example.

This is missing tests as I'm not sure how to write them yet.. Testing controllers is weird.

@ben-ng
Owner

p.s. the first few letters of the commit hash (ebdcbbc) make a cool jingle on the piano.

@ben-ng ben-ng referenced this pull request
Closed

Doesn't support DELETE? #382

@Techwraith
Owner

Awesome, looks a little complicated though, I'll have to digest it a bit more. @mde, thoughts?

@mde
Owner

This is good stuff. I think there may be some ways to make this a little bit cleaner, but this is an excellent first stab at it. I'll take a look at this over the next couple of days. I'd love for more people to chime in on this with ideas.

@ben-ng
Owner

Superseded by: #398

@ben-ng ben-ng closed this
@ben-ng ben-ng deleted the ben-ng:api-patch branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 10, 2013
  1. @ben-ng

    Handle API-style requests

    ben-ng authored
Commits on Jun 24, 2013
  1. @ben-ng
Commits on Jun 26, 2013
  1. @ben-ng
This page is out of date. Refresh to see the latest.
Showing with 134 additions and 16 deletions.
  1. +40 −16 gen/scaffold/controller.ejs
  2. +94 −0 lib/controller/base_controller.js
View
56 gen/scaffold/controller.ejs
@@ -1,5 +1,6 @@
var <%= names.constructor.plural %> = function () {
this.respondsWith = ['html', 'json', 'xml', 'js', 'txt'];
+ this.apiFormats = ['json','xml','js','txt'];
this.index = function (req, resp, params) {
var self = this;
@@ -15,20 +16,23 @@ var <%= names.constructor.plural %> = function () {
this.create = function (req, resp, params) {
var self = this
+ , onError = { redirect: {controller: "<%= names.property.plural %>", action: "add"} }
+ , onSuccess = { redirect: {controller: "<%= names.property.plural %>"} }
, <%= names.property.singular %> = geddy.model.<%= names.constructor.singular %>.create(params);
if (!<%= names.property.singular %>.isValid()) {
- this.flash.error(<%= names.property.singular %>.errors);
- this.redirect({action: 'add'});
+ self.displayError(<%= names.property.singular %>.errors, onError);
}
else {
<%= names.property.singular %>.save(function(err, data) {
if (err) {
- self.flash.error(err);
- self.redirect({action: 'add'});
+ self.displayError(err, onError);
}
else {
- self.redirect({controller: self.name});
+ onSuccess.redirect.id = data.id;
+ params.id = data.id;
+
+ self.displaySuccess("<%= names.constructor.singular %> Created", onSuccess);
}
});
}
@@ -36,6 +40,14 @@ var <%= names.constructor.plural %> = function () {
this.show = function (req, resp, params) {
var self = this;
+
+ if(!params.id) {
+ self.respond({
+ params: params
+ , errors:self.flash.error()
+ });
+ return;
+ }
geddy.model.<%= names.constructor.singular %>.first(params.id, function(err, <%= names.property.singular %>) {
if (!<%= names.property.singular %>) {
@@ -44,7 +56,11 @@ var <%= names.constructor.plural %> = function () {
self.error(err);
}
else {
- self.respond({params: params, <%= names.property.singular %>: <%= names.property.singular %>.toObj()});
+ self.respond({
+ params: params
+ , <%= names.property.singular %>: <%= names.property.singular %>.toObj()
+ , errors:self.flash.error()
+ });
}
});
};
@@ -65,22 +81,26 @@ var <%= names.constructor.plural %> = function () {
};
this.update = function (req, resp, params) {
- var self = this;
+ var self = this
+ , onError = {
+ redirect: {controller: "<%= names.property.plural %>", action: "edit", id: params.id}
+ }
+ , onSuccess = {
+ redirect: {controller: "<%= names.property.plural %>", id:params.id}
+ };
geddy.model.<%= names.constructor.singular %>.first(params.id, function(err, <%= names.property.singular %>) {
<%= names.property.singular %>.updateProperties(params);
if (!<%= names.property.singular %>.isValid()) {
- this.flash.error(<%= names.property.singular %>.errors);
- this.redirect({action: 'edit'});
+ self.displayError(<%= names.property.singular %>.errors, onError);
}
else {
<%= names.property.singular %>.save(function(err, data) {
if (err) {
- self.flash.error(err);
- self.redirect({action: 'edit'});
+ self.displayError(err, onError);
}
else {
- self.redirect({controller: self.name});
+ self.displaySuccess("<%= names.constructor.singular %> Updated", onSuccess);
}
});
}
@@ -88,15 +108,19 @@ var <%= names.constructor.plural %> = function () {
};
this.destroy = function (req, resp, params) {
- var self = this;
+ var self = this
+ , onError = { redirect: {controller: "<%= names.property.plural %>", action: "edit", id: params.id} }
+ , onSuccess = {
+ redirect: {controller: "<%= names.property.plural %>", action: "index"}
+ , transfer: null
+ };
geddy.model.<%= names.constructor.singular %>.remove(params.id, function(err) {
if (err) {
- self.flash.error(err);
- self.redirect({action: 'edit'});
+ self.displayError(err, onError);
}
else {
- self.redirect({controller: self.name});
+ self.displaySuccess("<%= names.constructor.singular %> Deleted", onSuccess);
}
});
};
View
94 lib/controller/base_controller.js
@@ -80,6 +80,14 @@ controller.BaseController = function () {
*/
this.respondsWith = ['txt'];
/**
+ @name controller.BaseController#apiFormats
+ @public
+ @type Array
+ @description Content-types to treat as API calls.
+ @default ['json','xml','js','txt']
+ */
+ this.apiFormats = ['json','xml','js','txt'];
+ /**
@name controller.BaseController#content
@public
@type {Object|String}
@@ -543,6 +551,82 @@ controller.BaseController.prototype = new (function () {
errors.respond(err, this.response);
};
+ this.displaySuccess = function (msg, routes) {
+ /*
+ @name controller.BaseController#displaySuccess
+ @public
+ @function
+ @description Indicate success in the current request
+ @param {String} msg The message to use as the basis for the response. e.g. "Zooby saved!"
+ @param {Object} [actions] Routes. Set API route to null to respond with an empty object, which is useful for DELETE requests.
+ @param {Object} [options.default] The default route to redirect to. Defaults to {action: "index"}.
+ @param {String} [options.api] The route to transfer to during an API call. Defaults to "show".
+ */
+ var routeDefaults = {
+ redirect: {
+ action: 'index'
+ }
+ , transfer: "show"
+ };
+
+ routes = routes || {};
+ routes = utils.enhance(routeDefaults, routes);
+
+ //Sets the success flash
+ this.flash.success(msg);
+
+ //Transfers instead of redirecting in the case of an API call to prevent 405 errors
+ if(this.isApiCall()) {
+ if(routes.transfer) {
+ this.transfer(routes.transfer);
+ }
+ else {
+ this.respond({});
+ }
+ }
+ else {
+ this.redirect(routes.redirect);
+ }
+ };
+
+ this.displayError = function (err, routes) {
+ /*
+ @name controller.BaseController#displayError
+ @public
+ @function
+ @description Display an error in the current request
+ @param {Object} err The error to use as the basis for the response. May be a string or dictionary e.g. {attribute:errorMessage}
+ @param {Object} [actions] Routes.
+ @param {Object} [options.default] The default route to redirect to. Defaults to {action: "index"}.
+ @param {String} [options.api] The route to transfer to during an API call. Defaults to "show".
+ */
+ var routeDefaults = {
+ redirect: {
+ action: 'index'
+ }
+ , transfer: "show"
+ };
+
+ routes = routes || {};
+ routes = utils.enhance(routeDefaults, routes);
+
+ //Sets the error flash
+ this.flash.error(err);
+
+ //Transfers instead of redirecting in the case of an API call to prevent 405 errors
+ if(this.isApiCall()) {
+ if(routes.transfer) {
+ this.transfer(routes.transfer);
+ }
+ else {
+ this.respond({});
+ }
+ }
+ else {
+ this.redirect(routes.redirect);
+ }
+ };
+
this.transfer = function (action) {
/*
@name controller.BaseController#transfer
@@ -686,6 +770,16 @@ controller.BaseController.prototype = new (function () {
}
};
+
+ this.isApiCall = function() {
+ /*
+ @name controller.BaseController#isApiCall
+ @public
+ @function
+ @description Returns true if the current request was an API call, false otherwise.
+ */
+ return (this.apiFormats.indexOf(this.params.format) > -1);
+ };
})();
Something went wrong with that request. Please try again.