Skip to content

Instantly share code, notes, and snippets.

@julp
Last active August 29, 2015 14:26
Show Gist options
  • Save julp/4cf561698184bea3c78f to your computer and use it in GitHub Desktop.
Save julp/4cf561698184bea3c78f to your computer and use it in GitHub Desktop.
Rails: export routes to Javascript
<% environment.context_class.instance_eval { include Rails.application.routes.url_helpers } %>
/* <generated by app/assets/javascripts/routes.coffee> */
var Route;
Route = (function() {
Route._routes = {};
function Route(name, path, pattern, segments, required, optionnal) {
this.name = name;
this.path = path;
this.pattern = pattern;
this.segments = segments;
this.required = required;
this.optionnal = optionnal;
Route._routes[this.name] = this;
}
Route.match = function(name) {
var i, res, ret, v, _i, _len;
if (name in this._routes && (res = window.location.pathname.match(this._routes[name].pattern))) {
ret = {};
res.shift();
for (i = _i = 0, _len = res.length; _i < _len; i = ++_i) {
v = res[i];
if (!!v) {
ret[this._routes[name].segments[i]] = v;
}
}
return ret;
} else {
return false;
}
};
Route.path = function(name, args) {
var k, path, _i, _j, _len, _len1, _ref, _ref1;
if (name in this._routes) {
path = this._routes[name].path;
_ref = this._routes[name].optionnal;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
k = _ref[_i];
if (k in args) {
path = path.replace(new RegExp('\\(([^)]*):' + k + '\\b([^)]*)\\)'), '$1' + args[k] + '$2');
delete args[k];
}
}
_ref1 = this._routes[name].required;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
k = _ref1[_j];
if (k in args) {
path = path.replace(new RegExp(':' + k + '\\b'), args[k]);
delete args[k];
}
}
path = path.replace(/\([^)]*\)/, '');
if (Object.keys(args).length) {
path += '?' + jQuery.param(args);
}
return path;
} else {
return false;
}
};
return Route;
})();
/* </generated by app/assets/javascripts/routes.coffee> */
<% Rails.application.routes.named_routes.routes.each_value do |r| %>
<% next if r.name =~ /(?:^|[^[[:alpha:]]])admin(?:[^[[:alpha:]]]|$)/ %> # skip any routes containing "admin"
new Route('<%= r.name %>', '<%= r.path.spec.to_s %>', /<%= r.path.to_regexp.to_s.sub(/\\A/, '^').sub(/\\Z/, '$').gsub(/\(\?-mix:/, '(?:') %>/, <%= r.segments.to_json %>, <%= r.required_parts.to_json %>, <%= r.optional_parts.to_json %>);
<% end %>

Usage

  1. In top of a prior app/assets/javascripts/XXX.js.erb (replace XXX by the name of your choice) file, add: <% environment.context_class.instance_eval { include Rails.application.routes.url_helpers } %>

  2. Copy the result of the command cat app/assets/javascripts/routes.coffee | coffee -scb into the same app/assets/javascripts/XXX.js.erb file.

  3. Expose the willed routes to Javascript:

<% Rails.application.routes.named_routes.routes.each_value do |r| %>
    <% next if r.name =~ /(?:^|[^[[:alpha:]]])admin(?:[^[[:alpha:]]]|$)/ %> # skip any routes containing "admin"
    new Route('<%= r.name %>', '<%= r.path.spec.to_s %>', /<%= r.path.to_regexp.to_s.sub(/\\A/, '^').sub(/\\Z/, '$').gsub(/\(\?-mix:/, '(?:') %>/, <%= r.segments.to_json %>, <%= r.required_parts.to_json %>, <%= r.optional_parts.to_json %>);
<% end %>

For lazy one, just grab the content of 0.js.erb

Prototype

Then, in your javascripts assets files, you should be able to use:

  • Route.match(name): test if current URL match the given named rails route (without any _path/_url suffix) and, if so, extract its parameters
  • Route.path(name [, args]): build path to the given named rails route. Optional args is a hash (object) of parameters (name => value)

Examples with routes from Getting Started with Rails:

if (false !== (params = Route.match('edit_article')) {
    console.log('You are editing article #' + params.id);
} else {
    // you are on an another location
}
$.get(
    Route.path('article', { id: 3, format: 'html' }), # => /articles/3.html
    function (data) {
        # do somehting with the result
    }
);

Route names can be "obfuscated"

Example with hashing them in md5:

For convenience, monkey patch String class:

require 'digest/md5'

class String
    def md5
        Digest::MD5.hexdigest self
    end
end
  • Replace new Route('<%= r.name %>' with new Route('<%= r.name.md5 %>'

  • Change extension of your asset file from .js to .erb.js if it is not already the case

  • Instead of 'name' as first argument of Route.path and Route.match, use '<%= 'name'.md5 %>'

Why not just use js-routes gem?

Because there is no equivalent to Route.match.

# cat app/assets/javascripts/routes.coffee | coffee -scb
class Route
@_routes: {}
constructor: (@name, @path, @pattern, @segments, @required, @optionnal) ->
Route._routes[@name] = this
@match: (name) ->
if name of @_routes and res = window.location.pathname.match(@_routes[name].pattern)
ret = {}
res.shift()
(ret[@_routes[name].segments[i]] = v if !!v) for v, i in res
return ret
else
return false
@path: (name, args) ->
if name of @_routes
path = @_routes[name].path
for k in @_routes[name].optionnal
if k of args
# Do not forget to escape \ itself while using the RegExp("pattern") notation because \ is also an escape character in strings.
path = path.replace(new RegExp('\\(([^)]*):' + k + '\\b([^)]*)\\)'), '$1' + args[k] + '$2')
delete args[k]
for k in @_routes[name].required
if k of args
path = path.replace(new RegExp(':' + k + '\\b'), args[k])
delete args[k]
#else
# error ?
# remove remaining (non substitued) optionnal arguments
path = path.replace(/\([^)]*\)/, '')
path += '?' + jQuery.param(args) if Object.keys(args).length
return path
else
return false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment