root / trunk / lib / camping-unabridged.rb

Revision 242, 25.2 kB (checked in by zimbatm, 6 months ago)

Yet another attempt to fix #147

Line 
1# == About camping.rb
2#
3# Camping comes with two versions of its source code.  The code contained in
4# lib/camping.rb is compressed, stripped of whitespace, using compact algorithms
5# to keep it tight.  The unspoken rule is that camping.rb should be flowed with
6# no more than 80 characters per line and must not exceed four kilobytes.
7#
8# On the other hand, lib/camping-unabridged.rb contains the same code, laid out
9# nicely with piles of documentation everywhere.  This documentation is entirely
10# generated from lib/camping-unabridged.rb using RDoc and our "flipbook" template
11# found in the extras directory of any camping distribution.
12#
13# == Requirements
14#
15# Camping requires at least Ruby 1.8.2.
16#
17# Camping depends on the following libraries.  If you install through RubyGems,
18# these will be automatically installed for you.
19#
20# * ActiveRecord, used in your models.
21#   ActiveRecord is an object-to-relational database mapper with adapters
22#   for SQLite3, MySQL, PostgreSQL, SQL Server and more.
23# * Markaby, used in your views to describe HTML in plain Ruby.
24# * MetAid, a few metaprogramming methods which Camping uses.
25# * Tempfile, for storing file uploads.
26#
27# Camping also works well with Mongrel, the swift Ruby web server.
28# http://rubyforge.org/projects/mongrel  Mongrel comes with examples
29# in its <tt>examples/camping</tt> directory.
30#
31%w[tempfile uri].map { |l| require l }
32
33class Object #:nodoc:
34  def meta_def(m,&b) #:nodoc:
35    (class<<self;self end).send(:define_method,m,&b)
36  end
37end
38
39# == Camping
40#
41# The camping module contains three modules for separating your application:
42#
43# * Camping::Models for your database interaction classes, all derived from ActiveRecord::Base.
44# * Camping::Controllers for storing controller classes, which map URLs to code.
45# * Camping::Views for storing methods which generate HTML.
46#
47# Of use to you is also one module for storing helpful additional methods:
48#
49# * Camping::Helpers which can be used in controllers and views.
50#
51# == The Camping Server
52#
53# How do you run Camping apps?  Oh, uh... The Camping Server!
54#
55# The Camping Server is, firstly and thusly, a set of rules.  At the very least, The Camping Server must:
56#
57# * Load all Camping apps in a directory.
58# * Load new apps that appear in that directory.
59# * Mount those apps according to their filename. (e.g. blog.rb is mounted at /blog.)
60# * Run each app's <tt>create</tt> method upon startup.
61# * Reload the app if its modification time changes.
62# * Reload the app if it requires any files under the same directory and one of their modification times changes.
63# * Support the X-Sendfile header.
64#
65# In fact, Camping comes with its own little The Camping Server.
66#
67# At a command prompt, run: <tt>camping examples/</tt> and the entire <tt>examples/</tt> directory will be served.
68#
69# Configurations also exist for Apache and Lighttpd.  See http://code.whytheluckystiff.net/camping/wiki/TheCampingServer.
70#
71# == The <tt>create</tt> method
72#
73# Many postambles will check for your application's <tt>create</tt> method and will run it
74# when the web server starts up.  This is a good place to check for database tables and create
75# those tables to save users of your application from needing to manually set them up.
76#
77#   def Blog.create
78#     unless Blog::Models::Post.table_exists?
79#       ActiveRecord::Schema.define do
80#         create_table :blog_posts, :force => true do |t|
81#           t.column :user_id,  :integer, :null => false
82#           t.column :title,    :string,  :limit => 255
83#           t.column :body,     :text
84#         end
85#       end
86#     end
87#   end
88#
89# For more tips, see http://code.whytheluckystiff.net/camping/wiki/GiveUsTheCreateMethod.
90module Camping
91  C = self
92  S = IO.read(__FILE__) rescue nil
93  P = '<h1>Cam\ping Problem!</h1><h2>%s</h2>'
94  # An object-like Hash.
95  # All Camping query string and cookie variables are loaded as this.
96  #
97  # To access the query string, for instance, use the <tt>@input</tt> variable.
98  #
99  #   module Blog::Controllers
100  #     class Index < R '/'
101  #       def get
102  #         if page = @input.page.to_i > 0
103  #           page -= 1
104  #         end
105  #         @posts = Post.find :all, :offset => page * 20, :limit => 20
106  #         render :index
107  #       end
108  #     end
109  #   end
110  #
111  # In the above example if you visit <tt>/?page=2</tt>, you'll get the second
112  # page of twenty posts.  You can also use <tt>@input[:page]</tt> or <tt>@input['page']</tt>
113  # to get the value for the <tt>page</tt> query variable.
114  #
115  # Use the <tt>@cookies</tt> variable in the same fashion to access cookie variables.
116  # Also, the <tt>@env</tt> variable is an H containing the HTTP headers and server info.
117  class H < Hash
118    # Gets or sets keys in the hash.
119    #
120    #   @cookies.my_favorite = :macadamian
121    #   @cookies.my_favorite
122    #   => :macadamian
123    #
124    def method_missing(m,*a)
125        m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super
126    end
127    alias u merge!
128    undef id, type
129  end
130
131  # Helpers contains methods available in your controllers and views.  You may add
132  # methods of your own to this module, including many helper methods from Rails.
133  # This is analogous to Rails' <tt>ApplicationHelper</tt> module.
134  #
135  # == Using ActionPack Helpers
136  #
137  # If you'd like to include helpers from Rails' modules, you'll need to look up the
138  # helper module in the Rails documentation at http://api.rubyonrails.org/.
139  #
140  # For example, if you look up the <tt>ActionView::Helpers::FormHelper</tt> class,
141  # you'll find that it's loaded from the <tt>action_view/helpers/form_helper.rb</tt>
142  # file.  You'll need to have the ActionPack gem installed for this to work.
143  #
144  #   require 'action_view/helpers/form_helper.rb'
145  #
146  #   # This example is unfinished.. soon..
147  #
148  module Helpers
149    # From inside your controllers and views, you will often need to figure out
150    # the route used to get to a certain controller +c+.  Pass the controller class
151    # and any arguments into the R method, a string containing the route will be
152    # returned to you.
153    #
154    # Assuming you have a specific route in an edit controller:
155    #
156    #   class Edit < R '/edit/(\d+)'
157    #
158    # A specific route to the Edit controller can be built with:
159    #
160    #   R(Edit, 1)
161    #
162    # Which outputs: <tt>/edit/1</tt>.
163    #
164    # You may also pass in a model object and the ID of the object will be used.
165    #
166    # If a controller has many routes, the route will be selected if it is the
167    # first in the routing list to have the right number of arguments.
168    #
169    # == Using R in the View
170    #
171    # Keep in mind that this route doesn't include the root path.
172    # You will need to use <tt>/</tt> (the slash method above) in your controllers.
173    # Or, go ahead and use the Helpers#URL method to build a complete URL for a route.
174    #
175    # However, in your views, the :href, :src and :action attributes automatically
176    # pass through the slash method, so you are encouraged to use <tt>R</tt> or
177    # <tt>URL</tt> in your views.
178    #
179    #  module Blog::Views
180    #    def menu
181    #      div.menu! do
182    #        a 'Home', :href => URL()
183    #        a 'Profile', :href => "/profile"
184    #        a 'Logout', :href => R(Logout)
185    #        a 'Google', :href => 'http://google.com'
186    #      end
187    #    end
188    #  end
189    #
190    # Let's say the above example takes place inside an application mounted at
191    # <tt>http://localhost:3301/frodo</tt> and that a controller named <tt>Logout</tt>
192    # is assigned to route <tt>/logout</tt>.  The HTML will come out as:
193    #
194    #   <div id="menu">
195    #     <a href="//localhost:3301/frodo/">Home</a>
196    #     <a href="/frodo/profile">Profile</a>
197    #     <a href="/frodo/logout">Logout</a>
198    #     <a href="http://google.com">Google</a>
199    #   </div>
200    #
201    def R(c,*g)
202      p,h=/\(.+?\)/,g.grep(Hash)
203      g-=h
204      raise "bad route" unless u = c.urls.find{|x|
205        break x if x.scan(p).size == g.size &&
206          /^#{x}\/?$/ =~ (x=g.inject(x){|x,a|
207            x.sub p,C.escape((a[a.class.primary_key]rescue a))})
208      }
209      h.any?? u+"?"+h[0].map{|x|x.map{|z|C.escape z}*"="}*"&": u
210    end
211
212    # Simply builds a complete path from a path +p+ within the app.  If your application is
213    # mounted at <tt>/blog</tt>:
214    #
215    #   self / "/view/1"    #=> "/blog/view/1"
216    #   self / "styles.css" #=> "styles.css"
217    #   self / R(Edit, 1)   #=> "/blog/edit/1"
218    #
219    def /(p); p[/^\//]?@root+p:p end
220    # Builds a URL route to a controller or a path, returning a URI object.
221    # This way you'll get the hostname and the port number, a complete URL.
222    # No scheme is given (http or https).
223    #
224    # You can use this to grab URLs for controllers using the R-style syntax.
225    # So, if your application is mounted at <tt>http://test.ing/blog/</tt>
226    # and you have a View controller which routes as <tt>R '/view/(\d+)'</tt>:
227    #
228    #   URL(View, @post.id)    #=> #<URL://test.ing/blog/view/12>
229    #
230    # Or you can use the direct path:
231    #
232    #   self.URL               #=> #<URL://test.ing/blog/>
233    #   self.URL + "view/12"   #=> #<URL://test.ing/blog/view/12>
234    #   URL("/view/12")        #=> #<URL://test.ing/blog/view/12>
235    #
236    # Since no scheme is given, you will need to add the scheme yourself:
237    #
238    #   "http" + URL("/view/12")   #=> "http://test.ing/blog/view/12"
239    #
240    # It's okay to pass URL strings through this method as well:
241    #
242    #   URL("http://google.com")  #=> #<URI:http://google.com>
243    #
244    # Any string which doesn't begin with a slash will pass through
245    # unscathed.
246    def URL c='/',*a
247      c = R(c, *a) if c.respond_to? :urls
248      c = self/c
249      c = "//"+@env.HTTP_HOST+c if c[/^\//]
250      URI(c)
251    end
252  end
253
254  # Camping::Base is built into each controller by way of the generic routing
255  # class Camping::R.  In some ways, this class is trying to do too much, but
256  # it saves code for all the glue to stay in one place.
257  #
258  # Forgivable, considering that it's only really a handful of methods and accessors.
259  #
260  # == Treating controller methods like Response objects
261  #
262  # Camping originally came with a barebones Response object, but it's often much more readable
263  # to just use your controller as the response.
264  #
265  # Go ahead and alter the status, cookies, headers and body instance variables as you
266  # see fit in order to customize the response.
267  #
268  #   module Camping::Controllers
269  #     class SoftLink
270  #       def get
271  #         redirect "/"
272  #       end
273  #     end
274  #   end
275  #
276  # Is equivalent to:
277  #
278  #   module Camping::Controllers
279  #     class SoftLink
280  #       def get
281  #         @status = 302
282  #         @headers['Location'] = "/"
283  #       end
284  #     end
285  #   end
286  #
287  module Base
288    attr_accessor :input, :cookies, :env, :headers, :body, :status, :root
289    Z = "\r\n"
290
291    # Display a view, calling it by its method name +m+.  If a <tt>layout</tt>
292    # method is found in Camping::Views, it will be used to wrap the HTML.
293    #
294    #   module Camping::Controllers
295    #     class Show
296    #       def get
297    #         @posts = Post.find :all
298    #         render :index
299    #       end
300    #     end
301    #   end
302    #
303    def render(m); end; undef_method :render
304
305    # Any stray method calls will be passed to Markaby.  This means you can reply
306    # with HTML directly from your controller for quick debugging.
307    #
308    #   module Camping::Controllers
309    #     class Info
310    #       def get; code @env.inspect end
311    #     end
312    #   end
313    #
314    # If you have a <tt>layout</tt> method in Camping::Views, it will be used to
315    # wrap the HTML.
316    def method_missing(*a,&b)
317      a.shift if a[0]==:render
318      m=Mab.new({},self)
319      s=m.capture{send(*a,&b)}
320      s=m.capture{send(:layout){s}} if /^_/!~a[0].to_s and m.respond_to?:layout
321      s
322    end
323
324    # A quick means of setting this controller's status, body and headers.
325    # Used internally by Camping, but... by all means...
326    #
327    #   r(302, '', 'Location' => self / "/view/12")
328    #
329    # Is equivalent to:
330    #
331    #   redirect "/view/12"
332    #
333    # See also: #r404, #r500 and #r501
334    def r(s, b, h = {}); @status = s; headers.u(h); @body = b; end
335
336    # Formulate a redirect response: a 302 status with <tt>Location</tt> header
337    # and a blank body.  Uses Helpers#URL to build the location from a controller
338    # route or path.
339    #
340    # So, given a root of <tt>http://localhost:3301/articles</tt>:
341    #
342    #   redirect "view/12"    # redirects to "//localhost:3301/articles/view/12"
343    #   redirect View, 12     # redirects to "//localhost:3301/articles/view/12"
344    #
345    # <b>NOTE:</b> This method doesn't magically exit your methods and redirect.
346    # You'll need to <tt>return redirect(...)</tt> if this isn't the last statement
347    # in your code.
348    def redirect(*a)
349      r(302,'','Location'=>URL(*a))
350    end
351
352    # Called when a controller was not found. It is mainly used internally, but it can
353    # also be useful for you, if you want to filter some parameters.
354    #
355    # module Camping
356    #   def r404(p=env.PATH)
357    #     @status = 404
358    #     div do
359    #       h1 'Camping Problem!'
360    #       h2 "#{p} not found"
361    #     end
362    #   end
363    # end
364    #
365    # See: I
366    def r404(p=env.PATH)
367      r(404, P % "#{p} not found")
368    end
369
370    # If there is a parse error in Camping or in your application's source code, it will not be caught
371    # by Camping.  The controller class +k+ and request method +m+ (GET, POST, etc.) where the error
372    # took place are passed in, along with the Exception +e+ which can be mined for useful info.
373    #
374    # You can overide it, but if you have an error in here, it will be uncaught !
375    #
376    # See: I
377    def r500(k,m,x)
378      r(500, P % "#{k}.#{m}" + "<h3>#{x.class} #{x.message}: <ul>#{x.backtrace.map{|b|"<li>#{b}</li>"}}</ul></h3>")
379    end
380
381    # Called if an undefined method is called on a Controller, along with the request method +m+ (GET, POST, etc.)
382    #
383    # See: I
384    def r501(m=@method)
385      r(501, P % "#{m.upcase} not implemented")
386    end
387
388    # Turn a controller into an array.  This is designed to be used to pipe
389    # controllers into the <tt>r</tt> method.  A great way to forward your
390    # requests!
391    #
392    #   class Read < '/(\d+)'
393    #     def get(id)
394    #       Post.find(id)
395    #     rescue
396    #       r *Blog.get(:NotFound, @env.REQUEST_URI)
397    #     end
398    #   end
399    #
400    def to_a;[status, body, headers] end
401
402    def initialize(r, e, m) #:nodoc:
403      @status, @method, @env, @headers, @root = 200, m, e,
404          H['Content-Type','text/html'], e.SCRIPT_NAME.sub(/\/$/,'')
405      @k = C.kp(e.HTTP_COOKIE)
406      q = C.qsp(e.QUERY_STRING)
407      @in = r
408      case e.CONTENT_TYPE
409      when %r|\Amultipart/form-.*boundary=\"?([^\";,]+)|n
410        b = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r$/
411        until @in.eof?
412          fh=H[]
413          for l in @in
414            case l
415            when Z: break
416            when /^Content-D.+?: form-data;/
417              fh.u H[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten]
418            when /^Content-Type: (.+?)(\r$|\Z)/m
419              fh.type = $1
420            end
421          end
422          fn=fh.name
423          o=if fh.filename
424            o=fh.tempfile=Tempfile.new(:C)
425            o.binmode
426          else
427            fh=""
428          end
429          s=8192
430          k=''
431          l=@in.read(s*2)
432          while l
433            if (k<<l)=~b
434              o<<$`.chomp
435              @in.seek(-$'.size,IO::SEEK_CUR)
436              break
437            end
438            o<<k.slice!(0...s)
439            l=@in.read(s)
440          end
441          C.qsp(fn,'&;',fh,q) if fn
442          fh.tempfile.rewind if fh.is_a?H
443        end
444      when "application/x-www-form-urlencoded"
445        q.u(C.qsp(@in.read))
446      end
447      @cookies, @input = @k.dup, q.dup
448    end
449
450    # All requests pass through this method before going to the controller.  Some magic
451    # in Camping can be performed by overriding this method.
452    #
453    # See http://code.whytheluckystiff.net/camping/wiki/BeforeAndAfterOverrides for more
454    # on before and after overrides with Camping.
455    def service(*a)
456      @body = send(@method, *a)
457      headers['Set-Cookie'] = cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] } - [nil]
458      self
459    end
460
461    # Used by the web server to convert the current request to a string.  If you need to
462    # alter the way Camping builds HTTP headers, consider overriding this method.
463    def to_s
464      "Status: #@status#{Z+(headers.inject([]){|a,o|[*o[1]].map{|v|a<<[o[0],v]*": "if v&&v.to_s.any?};a}*Z)+Z+Z}#@body"
465    end
466
467  end
468
469  # Controllers is a module for placing classes which handle URLs.  This is done
470  # by defining a route to each class using the Controllers::R method.
471  #
472  #   module Camping::Controllers
473  #     class Edit < R '/edit/(\d+)'
474  #       def get; end
475  #       def post; end
476  #     end
477  #   end
478  #
479  # If no route is set, Camping will guess the route from the class name.
480  # The rule is very simple: the route becomes a slash followed by the lowercased
481  # class name.  See Controllers::D for the complete rules of dispatch.
482  #
483  # == Special classes
484  #
485  # There are two special classes used for handling 404 and 500 errors.  The
486  # NotFound class handles URLs not found.  The ServerError class handles exceptions
487  # uncaught by your application.
488  X = module Controllers
489    @r = []
490    class << self
491      def r #:nodoc:
492        @r
493      end
494      # Add routes to a controller class by piling them into the R method.
495      #
496      #   module Camping::Controllers
497      #     class Edit < R '/edit/(\d+)', '/new'
498      #       def get(id)
499      #         if id   # edit
500      #         else    # new
501      #         end
502      #       end
503      #     end
504      #   end
505      #
506      # You will need to use routes in either of these cases:
507      #
508      # * You want to assign multiple routes to a controller.
509      # * You want your controller to receive arguments.
510      #
511      # Most of the time the rules inferred by dispatch method Controllers::D will get you
512      # by just fine.
513      def R *u
514        r=@r
515        Class.new {
516          meta_def(:urls){u}
517          meta_def(:inherited){|x|r<<x}
518        }
519      end
520
521      # Dispatch routes to controller classes.
522      # For each class, routes are checked for a match based on their order in the routing list
523      # given to Controllers::R.  If no routes were given, the dispatcher uses a slash followed
524      # by the name of the controller lowercased.
525      #
526      # Controllers are searched in this order:
527      #
528      # # Classes without routes, since they refer to a very specific URL.
529      # # Classes with routes are searched in order of their creation.
530      #
531      # So, define your catch-all controllers last.
532      def D(p, m)
533        r.map { |k|
534          k.urls.map { |x|
535            return (k.instance_method(m) rescue nil) ?
536              [k, m, *$~[1..-1]] : [I, 'r501', m] if p =~ /^#{x}\/?$/
537          }
538        }
539        [I, 'r404', p]
540      end
541
542      # The route maker, this is called by Camping internally, you shouldn't need to call it.
543      #
544      # Still, it's worth know what this method does.  Since Ruby doesn't keep track of class
545      # creation order, we're keeping an internal list of the controllers which inherit from R().
546      # This method goes through and adds all the remaining routes to the beginning of the list
547      # and ensures all the controllers have the right mixins.
548      #
549      # Anyway, if you are calling the URI dispatcher from outside of a Camping server, you'll
550      # definitely need to call this at least once to set things up.
551      def M
552        def M #:nodoc:
553        end
554        constants.map { |c|
555          k=const_get(c)
556          k.send :include,C,Base,Helpers,Models
557          @r=[k]+r if r-[k]==r
558          k.meta_def(:urls){["/#{c.downcase}"]}if !k.respond_to?:urls
559        }
560      end
561    end
562
563   
564    # Internal controller with no route. Used by #D and C.run to show internal messages.
565    class I < R()
566    end
567
568    self
569  end
570
571  class << self
572    # When you are running many applications, you may want to create independent
573    # modules for each Camping application.  Namespaces for each.  Camping::goes
574    # defines a toplevel constant with the whole MVC rack inside.
575    #
576    #   require 'camping'
577    #   Camping.goes :Blog
578    #
579    #   module Blog::Controllers; ... end
580    #   module Blog::Models;      ... end
581    #   module Blog::Views;       ... end
582    #
583    def goes(m)
584      eval S.gsub(/Camping/,m.to_s), TOPLEVEL_BINDING
585    end
586
587    # URL escapes a string.
588    #
589    #   Camping.escape("I'd go to the museum straightway!") 
590    #     #=> "I%27d+go+to+the+museum+straightway%21"
591    #
592    def escape(s); s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr(' ', '+') end
593
594    # Unescapes a URL-encoded string.
595    #
596    #   Camping.un("I%27d+go+to+the+museum+straightway%21")
597    #     #=> "I'd go to the museum straightway!"
598    #
599    def un(s); s.tr('+', ' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')} end
600
601    # Parses a query string into an Camping::H object.
602    #
603    #   input = Camping.qsp("name=Philarp+Tremain&hair=sandy+blonde")
604    #   input.name
605    #     #=> "Philarp Tremaine"
606    #
607    # Also parses out the Hash-like syntax used in PHP and Rails and builds
608    # nested hashes from it.
609    #
610    #   input = Camping.qsp("post[id]=1&post[user]=_why")
611    #     #=> {'post' => {'id' => '1', 'user' => '_why'}}
612    #
613    # And finally, Array syntax like:
614    #
615    #   input = Camping.qsp("user[]=_why&user[]=lucky&user[]=stiff")
616    #     #=> {"user" => ["_why", "lucky", "stiff"]}
617    #
618    def qsp(q, d='&;', y=nil, z=H[])
619        m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
620        (q.to_s.split(/[#{d}]+ */n) - [""]).
621            inject((b,z=z,H[])[0]) { |h,p|
622              k, v=un(p).split('=',2)
623                h.u(k.split(/[\]\[]+/).reverse.
624                    inject(y||v) { |x,i| H[i,x] },&m)
625            }
626    end
627
628    # Parses a string of cookies from the <tt>Cookie</tt> header.
629    def kp(s); c = qsp(s, ';,'); end
630
631    # Fields a request through Camping.  For traditional CGI applications, the method can be
632    # executed without arguments.
633    #
634    #   if __FILE__ == $0
635    #     Camping::Models::Base.establish_connection :adapter => 'sqlite3',
636    #         :database => 'blog3.db'
637    #     Camping::Models::Base.logger = Logger.new('camping.log')
638    #     puts Camping.run
639    #   end
640    #
641    # The Camping controller returned from <tt>run</tt> has a <tt>to_s</tt> method in case you
642    # are running from CGI or want to output the full HTTP output.  In the above example, <tt>puts</tt>
643    # will call <tt>to_s</tt> for you.
644    #
645    # For FastCGI and Webrick-loaded applications, you will need to use a request loop, with <tt>run</tt>
646    # at the center, passing in the read +r+ and write +w+ streams.  You will also need to mimick or
647    # pass in the <tt>ENV</tt> replacement as part of your wrapper.
648    #
649    # See Camping::FastCGI and Camping::WEBrick for examples.
650    #
651    def run(r=$stdin,e=ENV)
652      X.M
653      e = H[e.to_hash]
654      k,m,*a=X.D e.PATH_INFO=un("/#{e.PATH_INFO}".gsub(/\/+/,'/')),(e.REQUEST_METHOD||'get').downcase
655      k.new(r,e,m).Y.service(*a)
656    rescue => x
657      X::I.new(r,e,'r500').service(k,m,x)
658    end
659
660    # The Camping scriptable dispatcher.  Any unhandled method call to the app module will
661    # be sent to a controller class, specified as an argument.
662    #
663    #   Blog.get(:Index)
664    #   #=> #<Blog::Controllers::Index ... >
665    #
666    # The controller object contains all the @cookies, @body, @headers, etc. formulated by
667    # the response.
668    #
669    # You can also feed environment variables and query variables as a hash, the final
670    # argument.
671    #
672    #   Blog.post(:Login, :input => {'username' => 'admin', 'password' => 'camping'})
673    #   #=> #<Blog::Controllers::Login @user=... >
674    #
675    #   Blog.get(:Info, :env => {'HTTP_HOST' => 'wagon'})
676    #   #=> #<Blog::Controllers::Info @env={'HTTP_HOST'=>'wagon'} ...>
677    #
678    def method_missing(m, c, *a)
679      X.M
680      k = X.const_get(c).new(StringIO.new,
681             H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s)
682      H[a.pop].each { |e,f| k.send("#{e}=",f) } if Hash === a[-1]
683      k.service(*a)
684    end
685  end
686
687  # Views is an empty module for storing methods which create HTML.  The HTML is described
688  # using the Markaby language.
689  #
690  # == Using the layout method
691  #
692  # If your Views module has a <tt>layout</tt> method defined, it will be called with a block
693  # which will insert content from your view.
694  module Views; include X, Helpers end
695 
696  # Models is an empty Ruby module for housing model classes derived
697  # from ActiveRecord::Base.  As a shortcut, you may derive from Base
698  # which is an alias for ActiveRecord::Base.
699  #
700  #   module Camping::Models
701  #     class Post < Base; belongs_to :user end
702  #     class User < Base; has_many :posts end
703  #   end
704  #
705  # == Where Models are Used
706  #
707  # Models are used in your controller classes.  However, if your model class
708  # name conflicts with a controller class name, you will need to refer to it
709  # using the Models module.
710  #
711  #   module Camping::Controllers
712  #     class Post < R '/post/(\d+)'
713  #       def get(post_id)
714  #         @post = Models::Post.find post_id
715  #         render :index
716  #       end
717  #     end
718  #   end
719  #
720  # Models cannot be referred to in Views at this time.
721  module Models
722      autoload :Base,'camping/db'
723      def Y;self;end
724  end
725 
726  autoload :Mab, 'camping/mab'
727end
728
Note: See TracBrowser for help on using the browser.