Skip to content

Instantly share code, notes, and snippets.

@jejacks0n
Created August 1, 2010 23:15
Show Gist options
  • Save jejacks0n/503887 to your computer and use it in GitHub Desktop.
Save jejacks0n/503887 to your computer and use it in GitHub Desktop.
// Custom Rails routing/resource handling for ajax requests..
(function() {
var methods = ['get', 'put', 'post', 'delete'],
actions = ['index', 'show', 'new', 'create', 'edit', 'update', 'destroy'];
function parseUrl(url) {
var urlParts = url.match(/^((http[s]?|ftp):\/\/)?(((.+)@)?([^:\/\?#\s]+)(:(\d+))?)?(\/?[^\?#\.]+)?(\.([^\?#]+))?(\?([^#]?))?(#(.*))?$/i) || [];
var pathParts = (urlParts[9]) ? urlParts[9].match(/(\/.*)\/+(\w+)$/i) || [] : [];
return {scheme: urlParts[2], credentials: urlParts[5], host: urlParts[6], port: urlParts[8], path: urlParts[9], action: pathParts[2] || '', format: urlParts[11], query: urlParts[13], hash: urlParts[15]};
}
function observeAjax(original, url) {
var method = this.options.method.toLowerCase(),
urlParts = parseUrl(url),
path = urlParts.path,
action = urlParts.action,
handled = false;
for (var mapping in context.resources) {
var resource = context.resources[mapping],
actions = resource.__actions__,
f = false;
var matches = (path + '/').match(resource.__path__);
if (matches) {
var match1 = !matches[matches.length - 1],
match2 = !matches[matches.length - 2];
switch (method) {
case 'get':
if (match2) action = '';
switch (action) {
case '': f = actions['index']; break;
case 'new': f = actions['new']; break;
case 'edit': f = actions['edit']; break;
default: f = (match1) ? actions['show'] : actions['get'][action];
}
break;
case 'put': f = (match1) ? actions['update'] : actions['put'][action]; break;
case 'post': f = (match2) ? actions['create'] : actions['post'][action]; break;
case 'delete': f = (match1) ? actions['destroy'] : actions['delete'][action]; break;
}
var called = false;
var proceed = function(options) {
called = true;
this.options = Object.extend(this.options, options || {});
return original(url);
}.bind(this); // TODO: remove prototype dependancy
var result;
if (f) result = f(proceed, urlParts);
if (result !== false && !called) proceed(result);
handled = true;
}
}
if (!handled) return original(url);
}
function currentResource(path) {
if (this.resources[path]) return this.resources[path];
var resource = this.resources[path] = {
__actions__: {}, __path__: new RegExp('^/' + path + '(/(\\w+))?/?(\\w+)?/?$'),
add: function(actionOrActions, callback, method) {
if (typeof(actionOrActions) == 'string' && callback) {
var action = actionOrActions.split(':');
if (action.length > 1) this.__actions__[action[0]][action[1]] = callback;
else this.__actions__[action[0]] = callback;
} else if (typeof(actionOrActions) == 'object') {
var i;
for (i = 0; i < methods.length; i += 1) { this.__actions__[methods[i]] = actionOrActions[methods[i]] || this.__actions__[methods[i]] || {}; }
for (i = 0; i < actions.length; i += 1) { this.__actions__[actions[i]] = actionOrActions[actions[i]] || this.__actions__[actions[i]]; }
} else {
throw('To add a resource you must provide an action or method with a function, or an object of actions/methods');
}
}
};
var i;
for (i = 0; i < methods.length; i += 1) {
resource[methods[i]] = (function(method) {
return function(action, callback) {
resource.add(method + ':' + action, callback);
}
})(methods[i]);
}
for (i = 0; i < actions.length; i += 1) {
resource[actions[i]] = (function(action) {
return function(callback) { resource.add(action, callback) }
})(actions[i]);
}
return resource;
}
// TODO: add support for jQuery, mootools, YUI, Dojo, and ??
Ajax.Request.prototype.request = Ajax.Request.prototype.request.wrap(observeAjax);
// TODO: let's have a way to accomplish adding nicely to a window.Rails variable
// methods to add/manage resources for Rails
var context = window.Rails = {
resources: {},
resource: function(path, actions) {
var resource = currentResource.call(this, path);
resource.add(actions || {});
return resource;
}
};
})();
// GET /posts(.:format) index
// posts POST /posts(.:format) create
// new_post GET /posts/new(.:format) new
// GET /posts/:id(.:format) show
// PUT /posts/:id(.:format) update
// post DELETE /posts/:id(.:format) destroy
// edit_post GET /posts/:id/edit(.:format) edit
//---------------------------------------------------------------------------
// reorder_posts POST /posts/reorder(.:format)
// publish_post PUT /posts/:id/publish(.:format)
//deactivate_post_comment PUT /posts/:id/deactivate(.:format)
// activate_post_comment PUT /posts/:id/activate(.:format)
// approve_post_comment PUT /posts/:id/comment/approve(.:format)
// reject_post_comment PUT /posts/:id/comment/reject(.:format)
//
var RailsResourceTestClass = Class.create({
initialize: function() {
Rails.resource('namespace1/namespace2/posts', this.resourcePosts);
// TODO: it could be more like "blogs/:blog_id/posts"
Rails.resource('blogs/(\\w+)/posts', this.resourcesBlogs);
console.debug('----------- TESTING BASIC RESOURCES');
this.testResource('/posts');
console.debug('----------- TESTING NAMESPACED RESOURCES');
this.testResource('/namespace1/namespace2/posts');
console.debug('----------- TESTING NESTED RESOURCES');
this.testResource('/blogs/1/posts');
this.testResource('/blogs/2/posts');
console.debug('----------- TESTING TIMED CALLBACK / RESOURCE ADDITIONS');
this.testResource('/comments');
},
testResource: function(path) {
// default resource actions
new Ajax.Request(path, {method: 'get'}); // index
new Ajax.Request(path, {method: 'post'}); // create
new Ajax.Request(path+'/new', {method: 'get'}); // new
new Ajax.Request(path+'/1', {method: 'get'}); // show
new Ajax.Request(path+'/1', {method: 'put'}); // update
new Ajax.Request(path+'/1', {method: 'delete'}); // destroy
new Ajax.Request(path+'/1/edit', {method: 'get'}); // edit
// resource members
new Ajax.Request(path+'/reorder', {method: 'post'});
new Ajax.Request(path+'/1/publish', {method: 'put'});
new Ajax.Request(path+'/1/unset', {method: 'put'});
new Ajax.Request(path+'/1/comments', {method: 'get'});
new Ajax.Request(path+'/1/unset', {method: 'get'});
new Ajax.Request(path+'/1/comments', {method: 'delete'});
// unrelated resource -- shouldn't be handled
new Ajax.Request(path+'/1/comment/approve', {method: 'put'});
new Ajax.Request(path+'/1/comment/approve', {method: 'get'});
new Ajax.Request(path+'/1/comment/approve', {method: 'post'});
new Ajax.Request(path+'/1/comment/approve', {method: 'delete'});
new Ajax.Request(path+'/1/comment/reject', {method: 'put'});
},
resourcePosts: {
index: function() {
console.debug('Namespace::PostsController#index');
},
show: function() {
console.debug('Namespace::PostsController#show');
},
'new': function() {
console.debug('Namespace::PostsController#new');
},
create: function() {
console.debug('Namespace::PostsController#create');
},
edit: function() {
console.debug('Namespace::PostsController#edit');
},
update: function() {
console.debug('Namespace::PostsController#update');
},
destroy: function() {
console.debug('Namespace::PostsController#destroy');
},
post: {
reorder: function() {
console.debug('Namespace::PostsController#reorder:POST');
}
},
'delete': {
comments: function() {
console.debug('Namespace::PostsController#comments:DELETE');
}
},
get: {
comments: function() {
console.debug('Namespace::PostsController#comments:GET');
}
},
put: {
publish: function() {
console.debug('Namespace::PostsController#publish:PUT');
return {
onSuccess: function(response) {
console.debug('Namespace::PostsController#publish:success', response);
},
onFailure: function(response) {
console.debug('Namespace::PostsController#publish:failure', response);
}
}
}
}
},
resourcesBlogs: {
index: function() {
console.debug('Blogs::PostsController#index');
}
}
});
Rails.resource('posts', {
index: function() {
console.debug('PostsController#index');
},
show: function() {
console.debug('PostsController#show');
},
'new': function(proceed) {
console.debug('PostsController#new');
proceed();
},
create: function() {
console.debug('PostsController#create');
},
edit: function() {
console.debug('PostsController#edit');
},
update: function() {
console.debug('PostsController#update');
},
destroy: function() {
console.debug('PostsController#destroy');
},
post: {
reorder: function() {
console.debug('PostsController#reorder:POST');
}
},
'delete': {
comments: function() {
console.debug('PostsController#comments:DELETE');
}
},
get: {
comments: function() {
console.debug('PostsController#comments:GET');
}
},
put: {
publish: function() {
console.debug('PostsController#publish:PUT');
return {
onSuccess: function(response) {
console.debug('PostsController#publish:PUT:success', response);
},
onFailure: function(response) {
console.debug('PostsController#publish:PUT:failure', response);
}
}
}
}
});
Rails.resource('comments').add({
index: function(proceed) {
setTimeout(function() {
console.debug('CommentsController#index(in a setTimeout)');
proceed();
}, 10000);
return false;
},
destroy: function(proceed) {
console.debug('CommentsController#destroy');
proceed();
return true;
},
edit: function() {
console.debug('CommentsController#edit');
},
update: function() {
console.debug('CommentsController#update');
},
create: function() {
console.debug('CommentsController#create');
}
});
var comments = Rails.resource('comments', {
edit: function() {
console.debug('CommentsController#edit');
},
create: function() {
console.debug('CommentsController#create:override');
}
});
comments.add('update', function() {
console.debug('CommentsController#update:override');
});
comments.add('put:reject', function() {
console.debug('CommentsController#reject');
});
comments.edit(function() {
console.debug('CommentsController#edit:override');
});
comments.put('approve', function() {
console.debug('CommentsController#approve');
});
console.debug(Rails.resources);
new RailsResourceTestClass();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment