Sat Apr 03 16:22:17 +0200 2010

Camping, the Reference

Module Camping

If you’re new to Camping, you should probably start by reading the first chapters of The Camping Book.

Okay. So, the important thing to remember is that Camping.goes :Nuts copies the Camping module into Nuts. This means that you should never use any of these methods/classes on the Camping module, but rather on your own app. Here’s a short explanation on how Camping is organized:

Camping also ships with:

More importantly, Camping also installs The Camping Server, please see Camping::Server.

Methods

Public Class method: ::call(e)

Ruby web servers use this method to enter the Camping realm. The e argument is the environment variables hash as per the Rack specification. And array with [status, headers, body] is expected at the output.

See: rack.rubyforge.org/doc/SPEC.html

     # File lib/camping-unabridged.rb, line 584
584:     def call(e)
585:       X.M
586:       p = e['PATH_INFO'] = U.unescape(e['PATH_INFO'])
587:       k,m,*a=X.D p,e['REQUEST_METHOD'].downcase
588:       k.new(e,m).service(*a).to_a
589:     rescue
590:       r500(:I, k, m, $!, :env => e).to_a
591:     end

Public Class method: ::goes(m)

When you are running many applications, you may want to create independent modules for each Camping application. Camping::goes defines a toplevel constant with the whole MVC rack inside:

  require 'camping'
  Camping.goes :Nuts

  module Nuts::Controllers; ... end
  module Nuts::Models;      ... end
  module Nuts::Views;       ... end

All the applications will be available in Camping::Apps.

     # File lib/camping-unabridged.rb, line 575
575:     def goes(m)
576:       Apps << eval(S.gsub(/Camping/,m.to_s), TOPLEVEL_BINDING)
577:     end

Public Class method: ::method_missing(m, c, *a)

The Camping scriptable dispatcher. Any unhandled method call to the app module will be sent to a controller class, specified as an argument.

  Blog.get(:Index)
  #=> #<Blog::Controllers::Index ... >

The controller object contains all the @cookies, @body, @headers, etc. formulated by the response.

You can also feed environment variables and query variables as a hash, the final argument.

  Blog.post(:Login, :input => {'username' => 'admin', 'password' => 'camping'})
  #=> #<Blog::Controllers::Login @user=... >

  Blog.get(:Info, :env => {'HTTP_HOST' => 'wagon'})
  #=> #<Blog::Controllers::Info @headers={'HTTP_HOST'=>'wagon'} ...>
     # File lib/camping-unabridged.rb, line 611
611:     def method_missing(m, c, *a)
612:       X.M
613:       h = Hash === a[-1] ? a.pop : {}
614:       e = H[Rack::MockRequest.env_for('',h.delete(:env)||{})]
615:       k = X.const_get(c).new(e,m.to_s)
616:       h.each { |i, v| k.send("#{i}=", v) }
617:       k.service(*a)
618:     end

Public Class method: ::use(*a, &b)

Injects a middleware:

  module Blog
    use Rack::MethodOverride
    use Rack::Session::Memcache, :key => "session"
  end
     # File lib/camping-unabridged.rb, line 626
626:     def use(*a, &b)
627:       m = a.shift.new(method(:call), *a, &b)
628:       meta_def(:call) { |e| m.call(e) }
629:     end

Module Camping::Base

Camping::Base is built into each controller by way of the generic routing class Camping::R. In some ways, this class is trying to do too much, but it saves code for all the glue to stay in one place. Forgivable, considering that it’s only really a handful of methods and accessors.

Everything in this module is accessable inside your controllers.

Methods

Public Instance method: #mab(l=nil,&b)

You can directly return HTML form your controller for quick debugging by calling this method and pass some Markaby to it.

  module Nuts::Controllers
    class Info
      def get; mab{ code @headers.inspect } end
    end
  end

You can also pass true to use the :layout HTML wrapping method

     # File lib/camping-unabridged.rb, line 265
265:     def mab(l=nil,&b)
266:       m=Mab.new({},self)
267:       s=m.capture(&b)
268:       s=m.capture{layout{s}} if l && m.respond_to?(:layout)
269:       s
270:     end

Public Instance method: #r(s, b, h = {})

A quick means of setting this controller’s status, body and headers based on a Rack response:

  r(302, 'Location' => self / "/view/12", '')
  r(*another_app.call(@env))

You can also switch the body and the header if you want:

  r(404, "Could not find page")

See also: r404, r500 and r501

     # File lib/camping-unabridged.rb, line 283
283:     def r(s, b, h = {})
284:       b, h = h, b if Hash === b
285:       @status = s
286:       @headers.merge!(h)
287:       @body = b
288:     end

Public Instance method: #r404(p)

Called when a controller was not found. You can override this if you want to customize the error page:

  module Nuts
    def r404(path)
      @path = path
      render :not_found
    end
  end
     # File lib/camping-unabridged.rb, line 317
317:     def r404(p)
318:       P % "#{p} not found"
319:     end

Public Instance method: #r500(k,m,e)

Called when an exception is raised. However, if there is a parse error in Camping or in your application’s source code, it will not be caught.

k is the controller class, m is the request method (GET, POST, etc.) and e is the Exception which can be mined for useful info.

Be default this simply re-raises the error so a Rack middleware can handle it, but you are free to override it here:

  module Nuts
    def r500(klass, method, exception)
      send_email_alert(klass, method, exception)
      render :server_error
    end
  end
     # File lib/camping-unabridged.rb, line 336
336:     def r500(k,m,e)
337:       raise e
338:     end

Public Instance method: #r501(m)

Called if an undefined method is called on a controller, along with the request method m (GET, POST, etc.)

     # File lib/camping-unabridged.rb, line 342
342:     def r501(m)
343:       P % "#{m.upcase} not implemented"
344:     end

Public Instance method: #redirect(*a)

Formulate a redirect response: a 302 status with Location header and a blank body. Uses Helpers#URL to build the location from a controller route or path.

So, given a root of localhost:3301/articles:

  redirect "view/12"  # redirects to "//localhost:3301/articles/view/12"
  redirect View, 12   # redirects to "//localhost:3301/articles/view/12"

NOTE: This method doesn’t magically exit your methods and redirect. You’ll need to return redirect(...) if this isn’t the last statement in your code, or throw :halt if it’s in a helper.

See: Controllers

     # File lib/camping-unabridged.rb, line 304
304:     def redirect(*a)
305:       r(302,'','Location'=>URL(*a).to_s)
306:     end

Public Instance method: #render(v,*a,&b)

Display a view, calling it by its method name v. If a layout method is found in Camping::Views, it will be used to wrap the HTML.

  module Nuts::Controllers
    class Show
      def get
        @posts = Post.find :all
        render :index
      end
    end
  end
     # File lib/camping-unabridged.rb, line 251
251:     def render(v,*a,&b)
252:       mab(/^_/!~v.to_s){send(v,*a,&b)}
253:     end

Public Instance method: #service(*a)

All requests pass through this method before going to the controller. Some magic in Camping can be performed by overriding this method.

     # File lib/camping-unabridged.rb, line 390
390:     def service(*a)
391:       r = catch(:halt){send(@method, *a)}
392:       @body ||= r 
393:       self
394:     end

Public Instance method: #to_a()

Turn a controller into a Rack response. This is designed to be used to pipe controllers into the r method. A great way to forward your requests!

  class Read < '/(\d+)'
    def get(id)
      Post.find(id)
    rescue
      r *Blog.get(:NotFound, @headers.REQUEST_URI)
    end
  end
     # File lib/camping-unabridged.rb, line 357
357:     def to_a
358:       @env['rack.session'] = @state
359:       r = Rack::Response.new(@body, @status, @headers)
360:       @cookies.each do |k, v|
361:         next if @old_cookies[k] == v
362:         v = { :value => v, :path => self / "/" } if String === v
363:         r.set_cookie(k, v)
364:       end
365:       r.to_a
366:     end

Module Camping::Controllers

Controllers receive the requests and sends a response back to the client. A controller is simply a class which must implement the HTTP methods it wants to accept:

  module Nuts::Controllers
    class Index
      def get
        "Hello World"
      end
    end

    class Posts
      def post
        Post.create(@input)
        redirect Index
      end
    end
  end

Defining a controller

There are two ways to define controllers: Just defining a class and let Camping figure out the route, or add the route explicitly using R.

If you don’t use R, Camping will first split the controller name up by words (HelloWorld => Hello and World). Then it would do the following:

  • Replace Index with /
  • Replace X with ([^/]+)
  • Replace N with (\d+)
  • Everything else turns into lowercase
  • Join the words with slashes

Here’s a few examples:

  Index   # => /
  PostN   # => /post/(\d+)
  PageX   # => /page/([^/]+)
  Pages   # => /pages

The request

You have these variables which describes the request:

  • @env contains the environment as defined in rack.rubyforge.org/doc/SPEC.html
  • @request is Rack::Request.new(@env)
  • @root is the path where the app is mounted
  • @cookies is a hash with the cookies sent by the client
  • @state is a hash with the sessions (see Camping::Session)
  • @method is the HTTP method in lowercase

The response

You can change these variables to your needs:

  • @status is the HTTP status (defaults to 200)
  • @headers is a hash with the headers
  • @body is the body (a string or something which responds to each)
  • Any changes in @cookies and @state will also be sent to the client

If you haven’t set @body, it will use the return value of the method:

  module Nuts::Controllers
    class Index
      def get
        "This is the body"
      end
    end

    class Posts
      def get
        @body = "Hello World!"
        "This is ignored"
      end
    end
  end

Methods

Public Class method: ::D(p, m)

Dispatch routes to controller classes. For each class, routes are checked for a match based on their order in the routing list given to Controllers::R. If no routes were given, the dispatcher uses a slash followed by the name of the controller lowercased.

Controllers are searched in this order:

  • Classes without routes, since they refer to a very specific URL.
  • Classes with routes are searched in order of their creation.

So, define your catch-all controllers last.

     # File lib/camping-unabridged.rb, line 520
520:       def D(p, m)
521:         p = '/' if !p || !p[0]
522:         r.map { |k|
523:           k.urls.map { |x|
524:             return (k.instance_method(m) rescue nil) ?
525:               [k, m, *$~[1..-1]] : [I, 'r501', m] if p =~ /^#{x}\/?$/
526:           }
527:         }
528:         [I, 'r404', p]
529:       end

Public Class method: ::M()

The route maker, this is called by Camping internally, you shouldn’t need to call it.

Still, it’s worth know what this method does. Since Ruby doesn’t keep track of class creation order, we’re keeping an internal list of the controllers which inherit from R(). This method goes through and adds all the remaining routes to the beginning of the list and ensures all the controllers have the right mixins.

Anyway, if you are calling the URI dispatcher from outside of a Camping server, you’ll definitely need to call this to set things up. Don’t call it too early though. Any controllers added after this method is called won’t work properly

     # File lib/camping-unabridged.rb, line 545
545:       def M
546:         def M #:nodoc:
547:         end
548:         constants.map { |c|
549:           k = const_get(c)
550:           k.send :include,C,Base,Helpers,Models
551:           @r=[k]+r if r-[k]==r
552:           k.meta_def(:urls){["/#{c.scan(/.[^A-Z]*/).map(&N.method(:[]))*'/'}"]}if !k.respond_to?:urls
553:         }
554:       end

Public Class method: ::R(*u)

Add routes to a controller class by piling them into the R method.

The route is a regexp which will match the request path. Anything enclosed in parenthesis will be sent to the method as arguments.

  module Camping::Controllers
    class Edit < R '/edit/(\d+)', '/new'
      def get(id)
        if id   # edit
        else    # new
        end
      end
    end
  end
     # File lib/camping-unabridged.rb, line 501
501:       def R *u
502:         r=@r
503:         Class.new {
504:           meta_def(:urls){u}
505:           meta_def(:inherited){|x|r<<x}
506:         }
507:       end

Class Camping::H < Hash

An object-like Hash. All Camping query string and cookie variables are loaded as this.

To access the query string, for instance, use the @input variable.

  module Blog::Controllers
    class Index < R '/'
      def get
        if (page = @input.page.to_i) > 0
          page -= 1
        end
        @posts = Post.all, :offset => page * 20, :limit => 20
        render :index
      end
    end
  end

In the above example if you visit /?page=2, you’ll get the second page of twenty posts. You can also use @input['page'] to get the value for the page query variable.

Methods

Public Instance method: #method_missing(m,*a)

Gets or sets keys in the hash.

  @cookies.my_favorite = :macadamian
  @cookies.my_favorite
  => :macadamian
    # File lib/camping-unabridged.rb, line 77
77:     def method_missing(m,*a)
78:         m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super
79:     end

Module Camping::Helpers

Helpers contains methods available in your controllers and views. You may add methods of your own to this module, including many helper methods from Rails. This is analogous to Rails’ ApplicationHelper module.

Using ActionPack Helpers

If you’d like to include helpers from Rails’ modules, you’ll need to look up the helper module in the Rails documentation at api.rubyonrails.org/.

For example, if you look up the ActionView::Helpers::FormTagHelper class, you’ll find that it’s loaded from the action_view/helpers/form_tag_helper.rb file. You’ll need to have the ActionPack gem installed for this to work.

Often the helpers depends on other helpers, so you would have to look up the dependencies too. FormTagHelper for instance required the content_tag provided by TagHelper.

  require 'action_view/helpers/form_tag_helper'

  module Nuts::Helpers
    include ActionView::Helpers::TagHelper
    include ActionView::Helpers::FormTagHelper
  end

Return a response immediately

If you need to return a response inside a helper, you can use throw :halt.

  module Nuts::Helpers
    def requires_login!
      unless @state.user_id
        redirect Login
        throw :halt
      end
    end
  end

  module Nuts::Controllers
    class Admin
      def get
        requires_login!
        "Never gets here unless you're logged in"
      end
    end
  end

Methods

Public Instance method: #/(p)

Simply builds a complete path from a path p within the app. If your application is mounted at /blog:

  self / "/view/1"    #=> "/blog/view/1"
  self / "styles.css" #=> "styles.css"
  self / R(Edit, 1)   #=> "/blog/edit/1"
     # File lib/camping-unabridged.rb, line 198
198:     def /(p); p[0]==?/?@root+p:p end

Public Instance method: #R(c,*g)

From inside your controllers and views, you will often need to figure out the route used to get to a certain controller c. Pass the controller class and any arguments into the R method, a string containing the route will be returned to you.

Assuming you have a specific route in an edit controller:

  class Edit < R '/edit/(\d+)'

A specific route to the Edit controller can be built with:

  R(Edit, 1)

Which outputs: /edit/1.

If a controller has many routes, the route will be selected if it is the first in the routing list to have the right number of arguments.

Using R in the View

Keep in mind that this route doesn’t include the root path. You will need to use / (the slash method above) in your controllers. Or, go ahead and use the Helpers#URL method to build a complete URL for a route.

However, in your views, the :href, :src and :action attributes automatically pass through the slash method, so you are encouraged to use R or URL in your views.

 module Nuts::Views
   def menu
     div.menu! do
       a 'Home', :href => URL()
       a 'Profile', :href => "/profile"
       a 'Logout', :href => R(Logout)
       a 'Google', :href => 'http://google.com'
     end
   end
 end

Let’s say the above example takes place inside an application mounted at localhost:3301/frodo and that a controller named Logout is assigned to route /logout. The HTML will come out as:

  <div id="menu">
    <a href="http://localhost:3301/frodo/">Home</a>
    <a href="/frodo/profile">Profile</a>
    <a href="/frodo/logout">Logout</a>
    <a href="http://google.com">Google</a>
  </div>
     # File lib/camping-unabridged.rb, line 180
180:     def R(c,*g)
181:       p,h=/\(.+?\)/,g.grep(Hash)
182:       g-=h
183:       raise "bad route" unless u = c.urls.find{|x|
184:         break x if x.scan(p).size == g.size && 
185:           /^#{x}\/?$/ =~ (x=g.inject(x){|x,a|
186:             x.sub p,U.escape((a[a.class.primary_key]rescue a))})
187:       }
188:       h.any?? u+"?"+U.build_query(h[0]) : u
189:     end

Public Instance method: #URL(c='/',*a)

Builds a URL route to a controller or a path, returning a URI object. This way you’ll get the hostname and the port number, a complete URL.

You can use this to grab URLs for controllers using the R-style syntax. So, if your application is mounted at test.ing/blog/ and you have a View controller which routes as R '/view/(d+)':

  URL(View, @post.id)    #=> #<URL:http://test.ing/blog/view/12>

Or you can use the direct path:

  self.URL               #=> #<URL:http://test.ing/blog/>
  self.URL + "view/12"   #=> #<URL:http://test.ing/blog/view/12>
  URL("/view/12")        #=> #<URL:http://test.ing/blog/view/12>

It’s okay to pass URL strings through this method as well:

  URL("http://google.com")  #=> #<URL:http://google.com>

Any string which doesn’t begin with a slash will pass through unscathed.

     # File lib/camping-unabridged.rb, line 221
221:     def URL c='/',*a
222:       c = R(c, *a) if c.respond_to? :urls
223:       c = self/c
224:       c = @request.url[/.{8,}?(?=\/)/]+c if c[0]==?/
225:       URI(c)
226:     end

Module Camping::Models

Models is an empty Ruby module for housing model classes derived from ActiveRecord::Base. As a shortcut, you may derive from Base which is an alias for ActiveRecord::Base.

  module Camping::Models
    class Post < Base; belongs_to :user end
    class User < Base; has_many :posts end
  end

Where Models are Used

Models are used in your controller classes. However, if your model class name conflicts with a controller class name, you will need to refer to it using the Models module.

  module Camping::Controllers
    class Post < R '/post/(\d+)'
      def get(post_id)
        @post = Models::Post.find post_id
        render :index
      end
    end
  end

Models cannot be referred to in Views at this time.

Methods

Module Camping::Models::Base

Methods

Public Class method: ::table_name_prefix()

The default prefix for Camping model classes is the topmost module name lowercase and followed with an underscore.

  Tepee::Models::Page.table_name_prefix
    #=> "tepee_pages"
    # File lib/camping/ar.rb, line 66
66:     def Base.table_name_prefix
67:         "#{name[/\w+/]}_".downcase.sub(/^(#{A}|camping)_/i,'')
68:     end

Class Camping::Reloader < Object

The Camping Reloader

Camping apps are generally small and predictable. Many Camping apps are contained within a single file. Larger apps are split into a handful of other Ruby libraries within the same directory.

Since Camping apps (and their dependencies) are loaded with Ruby’s require method, there is a record of them in $LOADED_FEATURES. Which leaves a perfect space for this class to manage auto-reloading an app if any of its immediate dependencies changes.

Wrapping Your Apps

Since bin/camping and the Camping::Server class already use the Reloader, you probably don’t need to hack it on your own. But, if you’re rolling your own situation, here’s how.

Rather than this:

  require 'yourapp'

Use this:

  require 'camping/reloader'
  reloader = Camping::Reloader.new('/path/to/yourapp.rb')
  blog = reloader.apps[:Blog]
  wiki = reloader.apps[:Wiki]

The blog and wiki objects will behave exactly like your Blog and Wiki, but they will update themselves if yourapp.rb changes.

You can also give Reloader more than one script.

Methods

Public Class method: ::new(*scripts)

Creates the reloader, assigns a script to it and initially loads the application. Pass in the full path to the script, otherwise the script will be loaded relative to the current working directory.

     # File lib/camping/reloader.rb, line 150
150:     def initialize(*scripts)
151:       @scripts = []
152:       update(*scripts)
153:     end

Public Instance method: #apps()

Returns a Hash of all the apps available in the scripts, where the key would be the name of the app (the one you gave to Camping.goes) and the value would be the app (wrapped inside App).

     # File lib/camping/reloader.rb, line 185
185:     def apps
186:       @scripts.inject({}) do |hash, script|
187:         hash.merge(script.apps)
188:       end
189:     end

Public Instance method: #clear()

Removes all the scripts from the reloader.

     # File lib/camping/reloader.rb, line 173
173:     def clear
174:       @scrips = []
175:     end

Public Instance method: #reload!()

Simply calls reload! on all the Script objects.

     # File lib/camping/reloader.rb, line 178
178:     def reload!
179:       @scripts.each { |script| script.reload! }
180:     end

Public Instance method: #update(*scripts)

Updates the reloader to only use the scripts provided:

  reloader.update("examples/blog.rb", "examples/wiki.rb")
     # File lib/camping/reloader.rb, line 158
158:     def update(*scripts)
159:       old = @scripts.dup
160:       clear
161:       @scripts = scripts.map do |script|
162:         s = Script.new(script)
163:         if pos = old.index(s)
164:           # We already got a script, so we use the old (which might got a mtime)
165:           old[pos]
166:         else
167:           s.load_apps
168:         end
169:       end
170:     end

Class Camping::Reloader::App < (defined?(BasicObject) ? BasicObject : Object)

This is a simple wrapper which causes the script to reload (if needed) on any method call. Then the method call will be forwarded to the app.

Methods

Public Class method: ::new(script)

    # File lib/camping/reloader.rb, line 47
47:       def initialize(script)
48:         @script = script
49:       end

Public Instance method: #method_missing(meth, *args, &blk)

Reloads if needed, before calling the method on the app.

    # File lib/camping/reloader.rb, line 52
52:       def method_missing(meth, *args, &blk)
53:         @script.reload!
54:         @app.send(meth, *args, &blk)
55:       end

Class Camping::Server < Object

The Camping Server (for development)

Camping includes a pretty nifty server which is built for development. It follows these rules:

  • Load all Camping apps in a directory or a file.
  • Load new apps that appear in that directory or that file.
  • Mount those apps according to their name. (e.g. Blog is mounted at /blog.)
  • Run each app’s create method upon startup.
  • Reload the app if its modification time changes.
  • Reload the app if it requires any files under the same directory and one of their modification times changes.
  • Support the X-Sendfile header.

Run it like this:

  camping examples/        # Mounts all apps in that directory
  camping blog.rb          # Mounts Blog at /

And visit localhost:3301/ in your browser.

Methods

Public Class method: ::new(conf, paths)

    # File lib/camping/server.rb, line 29
29:   def initialize(conf, paths)
30:     @conf = conf
31:     @paths = paths
32:     @reloader = Camping::Reloader.new
33:     connect(@conf.database) if @conf.database
34:   end

Public Instance method: #app()

     # File lib/camping/server.rb, line 90
 90:   def app
 91:     reload!
 92:     all_apps = apps
 93:     rapp = case all_apps.length
 94:     when 0
 95:       proc{|env|[200,{'Content-Type'=>'text/html'},index_page([])]}
 96:     when 1
 97:       apps.values.first
 98:     else
 99:       hash = {
100:         "/" => proc {|env|[200,{'Content-Type'=>'text/html'},index_page(all_apps)]}
101:       }
102:       all_apps.each do |mount, wrapp|
103:         # We're doing @reloader.reload! ourself, so we don't need the wrapper.
104:         app = wrapp.app
105:         hash["/#{mount}"] = app
106:         hash["/code/#{mount}"] = proc do |env|
107:           [200,{'Content-Type'=>'text/plain','X-Sendfile'=>wrapp.script.file},'']
108:         end
109:       end
110:       Rack::URLMap.new(hash)
111:     end
112:     rapp = Rack::ContentLength.new(rapp)
113:     rapp = Rack::Lint.new(rapp)
114:     rapp = XSendfile.new(rapp)
115:     rapp = Rack::ShowExceptions.new(rapp)
116:   end

Public Instance method: #apps()

     # File lib/camping/server.rb, line 118
118:   def apps
119:     @reloader.apps.inject({}) do |h, (mount, wrapp)|
120:       h[mount.to_s.downcase] = wrapp
121:       h
122:     end
123:   end

Public Instance method: #call(env)

     # File lib/camping/server.rb, line 125
125:   def call(env)
126:     app.call(env)
127:   end

Public Instance method: #connect(db)

    # File lib/camping/server.rb, line 36
36:   def connect(db)
37:     unless Camping.autoload?(:Models)
38:       Camping::Models::Base.establish_connection(db)
39:     end
40:   end

Public Instance method: #find_scripts()

    # File lib/camping/server.rb, line 42
42:   def find_scripts
43:     scripts = @paths.map do |path|
44:       case
45:       when File.file?(path)
46:         path
47:       when File.directory?(path)
48:         Dir[File.join(path, '*.rb')]
49:       end
50:     end.flatten.compact
51:     @reloader.update(*scripts)
52:   end

Public Instance method: #index_page(apps)

    # File lib/camping/server.rb, line 54
54:   def index_page(apps)
55:     welcome = "You are Camping"
56:     header = "<html>\n<head>\n<title>\#{welcome}</title>\n<style type=\"text/css\">\nbody {\nfont-family: verdana, arial, sans-serif;\npadding: 10px 40px;\nmargin: 0;\n}\nh1, h2, h3, h4, h5, h6 {\nfont-family: utopia, georgia, serif;\n}\n</style>\n</head>\n<body>\n<h1>\#{welcome}</h1>\n"
57:     footer = '</body></html>'
58:     main = if apps.empty?
59:       "<p>Good day.  I'm sorry, but I could not find any Camping apps. "\
60:       "You might want to take a look at the console to see if any errors "\
61:       "have been raised.</p>"
62:     else
63:       "<p>Good day.  These are the Camping apps you've mounted.</p><ul>" + 
64:       apps.map do |mount, app|
65:         "<li><h3 style=\"display: inline\"><a href=\"/#{mount}\">#{app}</a></h3><small> / <a href=\"/code/#{mount}\">View source</a></small></li>"
66:       end.join("\n") + '</ul>'
67:     end
68:     
69:     header + main + footer
70:   end

Public Instance method: #reload!()

     # File lib/camping/server.rb, line 149
149:   def reload!
150:     find_scripts
151:     @reloader.reload!
152:   end

Public Instance method: #start()

     # File lib/camping/server.rb, line 129
129:   def start
130:     handler, conf = case @conf.server
131:     when "console"
132:       puts "** Starting console"
133:       reload!
134:       this = self; eval("self", TOPLEVEL_BINDING).meta_def(:reload!) { this.reload!; nil }
135:       ARGV.clear
136:       IRB.start
137:       exit
138:     when "mongrel"
139:       puts "** Starting Mongrel on #{@conf.host}:#{@conf.port}"
140:       [Rack::Handler::Mongrel, {:Port => @conf.port, :Host => @conf.host}]
141:     when "webrick"
142:       puts "** Starting WEBrick on #{@conf.host}:#{@conf.port}"
143:       [Rack::Handler::WEBrick, {:Port => @conf.port, :BindAddress => @conf.host}]
144:     end
145:     reload!
146:     handler.run(self, conf) 
147:   end

Class Camping::Server::XSendfile < Object

A Rack middleware for reading X-Sendfile. Should only be used in development.

Methods

Public Class method: ::new(app)

     # File lib/camping/server.rb, line 164
164:     def initialize(app)
165:       @app = app
166:     end

Public Instance method: #call(env)

     # File lib/camping/server.rb, line 168
168:     def call(env)
169:       status, headers, body = @app.call(env)
170:       headers = Rack::Utils::HeaderHash.new(headers)
171:       if header = HEADERS.detect { |header| headers.include?(header) }
172:         path = headers[header]
173:         body = File.read(path)
174:         headers['Content-Length'] = body.length.to_s
175:       end
176:       [status, headers, body]
177:     end

Module Camping::Session

Getting Started

To get sessions working for your application:

  1. require 'camping/session'
  2. Mixin the module: include Camping::Session
  3. Define a secret (and keep it secret): secret "SECRET!"
  4. Throughout your application, use the @state var like a hash to store your application’s data.
  require 'camping/session'    # 1

  module Nuts
    include Camping::Session   # 2
    secret "Oh yeah!"          # 3
  end

Other backends

Camping only ships with session-cookies. However, the @state variable is simply a shortcut for @env. Therefore you can also use any middleware which sets this variable:

  module Nuts
    use Rack::Session::Memcache
  end

Methods

Public Class method: ::included(app)

    # File lib/camping/session.rb, line 28
28:     def self.included(app)
29:       key    = "#{app}.state".downcase
30:       secret = [__FILE__, File.mtime(__FILE__)].join(":")
31:       
32:       app.meta_def(:secret) { |val| secret.replace(val) } 
33:       app.use Rack::Session::Cookie, :key => key, :secret => secret
34:     end

Module Camping::Views

Views is an empty module for storing methods which create HTML. The HTML is described using the Markaby language.

Defining and calling templates

Templates are simply Ruby methods with Markaby inside:

  module Blog::Views
    def index
      p "Welcome to my blog"
    end

    def show
      h1 @post.title
      self << @post.content
    end
  end

In your controllers you just call render :template_name which will invoke the template. The views and controllers will share instance variables (as you can see above).

Using the layout method

If your Views module has a layout method defined, it will be called with a block which will insert content from your view:

  module Blog::Views
    def layout
      html do
        head { title "My Blog "}
        body { self << yield }
      end
    end
  end

Methods