javascript_routes/lib/javascript_routes.rb

171 lines
5.5 KiB
Ruby

module JavascriptRoutes
JS = File.join(File.dirname(__FILE__), 'javascripts', 'routes.js')
JS_PACKED = File.join(File.dirname(__FILE__), 'javascripts', 'routes-min.js')
JS_AJAX = File.join(File.dirname(__FILE__), 'javascripts', 'routes-ajax.js')
FILENAME = File.join(RAILS_ROOT, 'public', 'javascripts', 'routes.js')
FILENAME_AJAX = File.join(RAILS_ROOT, 'public', 'javascripts', 'routes-ajax.js')
# Generate...
#
# Options are:
# :filename => name of routes javascript file (default routes.js)
# :filename_ajax => name of routes ajax-extras (default routes-ajax.js)
#
# :lite => only generate functions, not the unnamed generational routes (i.e. from controller/action)
#
# :pack => use the packed version
#
# :routes - which routes (leave out for all)
# :named_routes - which named routes (leave out for all)
#
#
def self.generate(options = {})
options.symbolize_keys!.reverse_merge!(:pack => true)
routes = options[:routes] || processable_routes
named_routes = options[:named_routes] || processable_named_routes
filename = options[:filename] || FILENAME
filename_ajax = options[:filename_ajax] || FILENAME_AJAX
# Create one function per route (simple lite version...)
if options[:lite]
generate_lite(named_routes, filename)
# Simulate Rails route generation logic -- not plain functions
# (includes lib/routes.js)
else
generate_full(named_routes, routes, filename, options)
# Add ajax extras
File.open filename_ajax, 'w' do |f|
f.write(File.read(JS_AJAX))
end
end
rescue => e
warn("\n\nCould not write routes.js: \"#{e.class}:#{e.message}\"\n\n")
File.truncate(filename, 0) rescue nil
end
def generate_lite(named_routes, filename)
route_functions = named_routes.map do |name, route|
processable_segments = route.segments.select{|s| processable_segment(s)}
# Generate the tokens that make up the single statement in this fn
tokens = processable_segments.inject([]) {|tokens, segment|
is_var = segment.respond_to?(:key)
prev_is_var = tokens.last.is_a?(Symbol)
value = (is_var ? segment.key : segment.to_s)
# Is the previous token ammendable?
require_new_token = (tokens.empty? || is_var || prev_is_var)
(require_new_token ? tokens : tokens.last) << value
tokens
}
# Convert strings to have quotes, and concatenate...
statement = tokens.map{|t| t.is_a?(Symbol) ? t : "\"#{t}\""}.join("+")
fn_params = processable_segments.select{|s|s.respond_to?(:key)}.map(&:key)
"#{name}_path: function(#{fn_params.join(', ')}) {return #{statement};}"
end
File.open(filename, 'w') do |file|
file << "var Routes = (function(){\n"
file << " return {\n"
file << " " + route_functions.join(",\n ") + "\n"
file << " }\n"
file << "})();"
end
end
# Generates all routes (named or unnamed) as an array of JSON objects
#
# The routes are encoded to reduce size:
# - each is an object with keys: 'n' for name, 's' for segments & 'r' for requirements
#
# Segments are further encode:
# - path segments indicated by first char being set to '*'
# - otherwise, the segment is just to_s
# - 't' or 'f' is appended to indicate is_optional
def self.generate_full(named_routes, routes, filename, options)
# Builds a JS array with a hash for each route
routes_array = routes.map{|r|
# Encode segments
encoded_segments = r.segments.select{|s|processable_segment(s)}.map{|s|
if s.is_a?(ActionController::Routing::PathSegment)
'*' + s.to_s[1..-1] + (s.is_optional ? 't' : 'f')
else
s.to_s + (s.is_optional ? 't' : 'f')
end
}
# Generate route as a hash
route_hash = {
's' => encoded_segments.join('@'), # 's' -- for segments
'r' => r.requirements # 'r' -- for requirements (params for a valid generation)
}
named_route = named_routes.find{|name,route| route.equal?(r) }
if named_route
route_hash['n'] = named_route.first # 'n' -- for name of named route
end
route_hash
}
File.open(filename, 'w') do |file|
# Add core JS (external file)
file << File.read(options[:pack] ? JS_PACKED : JS)
js = <<-JS
var url_root = #{ActionController::Base.relative_url_root.to_json};
var routes_array = #{routes_array.to_json};
for (var i = 0; i < routes_array.length; i++) {
var route = routes_array[i]; var segments = [];
var segment_strings = route.s.split('@');
for (var j = 0; j < segment_strings.length; j++) {
var segment_string = segment_strings[j];
segments.push(Route.S(segment_string.slice(0, -1), segment_string.slice(-1) == 't'));
}
Routes.push(new Route(segments, route.r, route.n));
}
Routes.extractNamed();
JS
# Wrap in a function (called straight away) to not pollute namespace
file << "\n(function(){\n#{js}\n})();"
end
end
def self.processable_segment(segment)
!segment.is_a?(ActionController::Routing::OptionalFormatSegment)
end
def self.processable_routes
ActionController::Routing::Routes.routes.select{|r|
r.conditions[:method].nil? || r.conditions[:method] == :get
}
end
def self.processable_named_routes
ActionController::Routing::Routes.named_routes.routes.select{|n,r|
r.conditions[:method].nil? || r.conditions[:method] == :get
}
end
end