root / tags / 1.5 / lib / camping-unabridged.rb

Revision 162, 26.0 kB (checked in by zimbatm, 2 years ago)

Now render doesn't render the layout around partials. See #76.

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