Permalink
Browse files

Inferred routes plugin

  • Loading branch information...
0 parents commit b50f9c5de00b847b4eb69ecd336036e0a50a0925 dblack committed Apr 19, 2008
Showing with 467 additions and 0 deletions.
  1. +20 −0 MIT-LICENSE
  2. +113 −0 README
  3. +22 −0 Rakefile
  4. +11 −0 init.rb
  5. +1 −0 install.rb
  6. +135 −0 lib/inferred_routes.rb
  7. +4 −0 tasks/inferred_routes_tasks.rake
  8. +160 −0 test/inferred_routes_test.rb
  9. +1 −0 uninstall.rb
@@ -0,0 +1,20 @@
+Copyright (c) 2006 David A. Black
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
113 README
@@ -0,0 +1,113 @@
+InferredRoutes
+==============
+
+DESCRIPTION
+
+This plugin lets you do shorter versions of named nested RESTful
+routes. I've written it because I don't like to have to inline
+the objects from which the route elements need to be gleaned, if
+those objects can be inferred from ONE object.
+
+For example: given a nest of routes:
+
+ map.resources :schools do |s|
+ s.resources :departments do |d|
+ d.resources :teachers
+ end
+ end
+
+I don't like to have to specify all of the components of the nest,
+like this:
+
+ teacher_url(@school,@department,@teacher)
+
+I would rather just do:
+
+ teacher_url(@teacher)
+
+and have the nest be inferred, as if I'd written:
+
+ teacher_url(@teacher.department.school, @teacher.department, @teacher)
+
+Similarly, I'd like a plural route:
+
+ teachers_url(@department)
+
+to be interpreted as:
+
+ teachers_url(@department.school, @department)
+
+I'd even like to "fall off the cliff" and specify a route using
+something that isn't technically part of it:
+
+ teachers_url(@teacher)
+
+expanding to:
+
+ teachers_url(@teacher.department.school, @teacher.department)
+
+There's no teacher in the route, but this lets me generate, say, a URL
+for creating a new teacher in the same department as a known teacher.
+
+(The variables don't have to be instance variables; that's just for
+consistency in the examples.)
+
+
+USAGE
+
+SINGULAR:
+
+ a_url(z,y,x,...c,b,a)
+
+becomes:
+
+ a_url(a)
+
+and z,y,x...c,b will be inferred. The nest has to match the object
+associations; that is, all of these must be present:
+
+ a.b == some_b
+ some_b.c == some_c
+ ...
+ some_y.z == some_z
+
+
+PLURAL:
+
+ as_url(z,y,x,...b)
+
+becomes:
+
+ as_url(b)
+
+You can also use an 'a':
+
+ as_url(a)
+
+and the b-object will be gleaned from the a. The cascade of object
+associations has to work all the way through the list.
+
+
+TESTING
+
+I've got tests against a particular application I've written. (They
+all pass :-) I haven't packaged the whole test application here.
+I'll work on a non-cumbersome way of doing that....
+
+Please report any bugs or improvements to me. Thanks.
+
+
+AUTHOR
+
+inferred_routes is by David A. Black, Director of Ruby Power and
+Light, LLC.
+
+
+VERSION
+
+This is inferred_routes version 0.1.0.
+
+
+COPYRIGHT AND WARRANTY
+
+See file "MIT-LICENSE"
@@ -0,0 +1,22 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the inferred_routes plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the inferred_routes plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'InferredRoutes'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
11 init.rb
@@ -0,0 +1,11 @@
+# inferred_routes plugin
+#
+# init.rb
+#
+# Version 0.1.1
+#
+# David A. Black
+#
+# November 1, 2006
+
+require 'inferred_routes'
@@ -0,0 +1 @@
+# Install hook code here
@@ -0,0 +1,135 @@
+# lib/inferred_routes.rb
+#
+# Version 0.1.1
+#
+# David A. Black
+#
+# November 1, 2006
+#
+# This is simply the define_url_helper method from ActionController,
+# with a big hack from me to make it infer routes.
+
+ class ActionController::Routing::RouteSet::NamedRouteCollection
+
+
+ def define_url_helper(route, name, kind, options)
+ selector = url_helper_name(name, kind)
+ hash_access_method = hash_access_name(name, kind)
+
+ @module.module_eval <<-end_eval # We use module_eval to avoid leaks
+ def #{selector}(*args)
+ #{generate_optimisation_block(route, kind)}
+
+ keys = #{route.segment_keys.inspect}
+ opts = if args.empty? || Hash === args.first
+ args.first || {}
+ else
+ options = args.last.is_a?(Hash) ? args.pop : {}
+
+ sing = keys[-1] == :id
+ items = keys.map {|k| k.to_s[/[^_]+/] }
+
+ if sing
+ items.pop
+ until args.size == keys.size
+ args.unshift(args[0].send(items.pop))
+ end
+ else
+ if args.size == 1
+# items: ["scratchpad","page"]
+# args: [@page]
+ if args[0].class.name == items[-1].classify
+ base_obj = args[0]
+ items.pop
+ else
+# items: ["scratchpad", "page"]
+# args: [@idea]
+ base_obj = args.shift
+ end
+ items.reverse.each do |item|
+ args.unshift(base_obj.send(item))
+ base_obj = args[0]
+ end
+ end
+ end
+ args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
+ v ||= ""
+ h[k] = v
+ h
+ end
+ options.merge(args)
+ end
+ url_for(#{hash_access_method}(opts))
+ end
+ protected :#{selector}
+ end_eval
+ helpers << selector
+ end
+
+
+ def efine_url_helper(route, name, kind, options)
+ selector = url_helper_name(name, kind)
+
+ # The segment keys used for positional paramters
+
+ segment_keys = route.segments.collect do |segment|
+ segment.key if segment.respond_to? :key
+ end.compact
+ hash_access_method = hash_access_name(name, kind)
+ @module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks
+ def #{selector}(*args)
+
+# Hack from dblack.
+ keys = #{segment_keys.inspect}
+
+ sing = keys[-1] == :id
+ items = keys.map {|k| k.to_s[/[^_]+/] }
+
+ if sing
+ items.pop
+ until args.size == keys.size
+ args.unshift(args[0].send(items.pop))
+ end
+ else
+ if args.size == 1
+# items: ["scratchpad","page"]
+# args: [@page]
+ if args[0].class.name == items[-1].classify
+ base_obj = args[0]
+ items.pop
+ else
+# items: ["scratchpad", "page"]
+# args: [@idea]
+ base_obj = args.shift
+ end
+ items.reverse.each do |item|
+ args.unshift(base_obj.send(item))
+ base_obj = args[0]
+ end
+ end
+ end
+# End hack.
+ opts = if args.empty? || Hash === args.first
+ args.first || {}
+ else
+ # allow ordered parameters to be associated with corresponding
+ # dynamic segments, so you can do
+ #
+ # foo_url(bar, baz, bang)
+ #
+ # instead of
+ #
+ # foo_url(:bar => bar, :baz => baz, :bang => bang)
+
+ args.zip(#{segment_keys.inspect}).inject({}) do |h, (v, k)|
+ h[k] = v
+ h
+ end
+ end
+ url_for(#{hash_access_method}(opts))
+ end
+ end_eval
+ @module.send(:protected, selector)
+ helpers << selector
+ end
+ end
@@ -0,0 +1,4 @@
+# desc "Explaining what the task does"
+# task :inferred_routes do
+# # Task goes here
+# end
Oops, something went wrong.

0 comments on commit b50f9c5

Please sign in to comment.