lib/roda/plugins/path.rb in roda-2.0.0 vs lib/roda/plugins/path.rb in roda-2.1.0

- old
+ new

@@ -1,44 +1,97 @@ class Roda module RodaPlugins # The path plugin adds support for named paths. Using the +path+ class method, you can # easily create <tt>*_path</tt> instance methods for each named path. Those instance # methods can then be called if you need to get the path for a form action, link, - # redirect, or anything else. Example: + # redirect, or anything else. # + # Additionally, you can call the +path+ class method with a class and a block, and it will register + # the class. You can then call the +path+ instance method with an instance of that class, and it will + # instance_exec the block with the arguments provided to path. + # + # Example: + # # plugin :path # path :foo, '/foo' # path :bar do |bar| # "/bar/#{bar.id}" # end + # path Baz do |baz, *paths| + # "/baz/#{baz.id}/#{paths.join('/')}" + # end + # path Quux |quux, path| + # "/quux/#{quux.id}/#{path}" + # end # # route do |r| + # r.post 'foo' do + # r.redirect foo_path # /foo + # end + # # r.post 'bar' do # bar = Bar.create(r.params['bar']) - # r.redirect bar_path(bar) + # r.redirect bar_path(bar) # /bar/1 # end + # + # r.post 'baz' do + # bar = Baz[1] + # r.redirect path(baz, 'c', 'd') # /baz/1/c/d + # end + # + # r.post 'quux' do + # bar = Quux[1] + # r.redirect path(quux, '/bar') # /quux/1/bar + # end # end # - # The path method accepts the following options: + # The path method accepts the following options when not called with a class: # - # :add_script_name :: Prefix the path generated with SCRIPT_NAME. + # :add_script_name :: Prefix the path generated with SCRIPT_NAME. This defaults to the app's + # :add_script_name option. # :name :: Provide a different name for the method, instead of using <tt>*_path</tt>. # :url :: Create a url method in addition to the path method, which will prefix the string generated # with the appropriate scheme, host, and port. If true, creates a <tt>*_url</tt> # method. If a Symbol or String, uses the value as the url method name. # :url_only :: Do not create a path method, just a url method. # - # Note that if :add_script_name, :url, or :url_only is used, this will also create a <tt>_*_path</tt> + # Note that if :add_script_name, :url, or :url_only is used, will also create a <tt>_*_path</tt> # method. This is necessary in order to support path methods that accept blocks, as you can't pass # a block to a block that is instance_execed. module Path DEFAULT_PORTS = {'http' => 80, 'https' => 443}.freeze OPTS = {}.freeze + # Initialize the path classes when loading the plugin + def self.configure(app) + app.instance_eval do + @path_classes ||= {} + unless @path_classes[String] + path(String){|str| str} + end + end + end + module ClassMethods + # Hash of recognizes classes for path instance method. Keys are classes, values are procs. + attr_reader :path_classes + + # Freeze the path classes when freezing the app. + def freeze + @path_classes.freeze + super + end + # Create a new instance method for the named path. See plugin module documentation for options. def path(name, path=nil, opts=OPTS, &block) + if name.is_a?(Class) + raise RodaError, "can't provide path or options when calling path with a class" unless path.nil? && opts.empty? + raise RodaError, "must provide a block when calling path with a class" unless block + @path_classes[name] = block + return + end + if path.is_a?(Hash) raise RodaError, "cannot provide two option hashses to Roda.path" unless opts.empty? opts = path path = nil end @@ -51,11 +104,11 @@ block = lambda{path} end meth = opts[:name] || "#{name}_path" url = opts[:url] - add_script_name = opts[:add_script_name] + add_script_name = opts.fetch(:add_script_name, self.opts[:add_script_name]) if add_script_name || url || opts[:url_only] _meth = "_#{meth}" define_method(_meth, &block) end @@ -89,9 +142,25 @@ define_method(url_meth, &url_block) end nil + end + end + + module InstanceMethods + # Return a path based on the class of the object. The object passed must have + # had its class previously registered with the application. If the app's + # :add_script_name option is true, this prepends the SCRIPT_NAME to the path. + def path(obj, *args) + app = self.class + if blk = app.path_classes[obj.class] + path = instance_exec(obj, *args, &blk) + path = request.script_name.to_s + path if app.opts[:add_script_name] + path + else + raise RodaError, "unrecognized object given to Roda#path: #{obj.inspect}" + end end end end register_plugin(:path, Path)