Thu Aug 19 16:07:43 +0200 2010
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.
Camping includes a pretty nifty server which is built for development. It follows these rules:
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.
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
[ show source ]
# File lib/camping-unabridged.rb, line 609
609: def call(e)
610: X.M
611: p = e['PATH_INFO'] = U.unescape(e['PATH_INFO'])
612: k,m,*a=X.D p,e['REQUEST_METHOD'].downcase,e
613: k.new(e,m).service(*a).to_a
614: rescue
615: r500(:I, k, m, $!, :env => e).to_a
616: end
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.
[ show source ]
# File lib/camping-unabridged.rb, line 600
600: def goes(m)
601: Apps << eval(S.gsub(/Camping/,m.to_s), TOPLEVEL_BINDING)
602: end
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'} ...>
[ show source ]
# File lib/camping-unabridged.rb, line 636
636: def method_missing(m, c, *a)
637: X.M
638: h = Hash === a[-1] ? a.pop : {}
639: e = H[Rack::MockRequest.env_for('',h.delete(:env)||{})]
640: k = X.const_get(c).new(e,m.to_s)
641: h.each { |i, v| k.send("#{i}=", v) }
642: k.service(*a)
643: end
A hash where you can set different settings.
[ show source ]
# File lib/camping-unabridged.rb, line 657
657: def options
658: O
659: end
Shortcut for setting options:
module Blog
set :secret, "Hello!"
end
[ show source ]
# File lib/camping-unabridged.rb, line 666
666: def set(k, v)
667: O[k] = v
668: end
Injects a middleware:
module Blog
use Rack::MethodOverride
use Rack::Session::Memcache, :key => "session"
end
[ show source ]
# File lib/camping-unabridged.rb, line 651
651: def use(*a, &b)
652: m = a.shift.new(method(:call), *a, &b)
653: meta_def(:call) { |e| m.call(e) }
654: end
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.
Finds a template, returning either:
false # => Could not find template true # => Found template in Views instance of Tilt # => Found template in a file
[ show source ]
# File lib/camping-unabridged.rb, line 250
250: def lookup(n)
251: T.fetch(n.to_sym) do |k|
252: t = Views.method_defined?(k) ||
253: (f = Dir[[O[:views] || "views", "#{n}.*"]*'/'][0]) &&
254: Template.new(f, O[f[/\.(\w+)$/, 1].to_sym] || {})
255:
256: O[:dynamic_templates] ? t : T[k] = t
257: end
258: end
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
[ show source ]
# File lib/camping-unabridged.rb, line 293
293: def mab(&b)
294: (@mab ||= Mab.new({},self)).capture(&b)
295: end
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")
[ show source ]
# File lib/camping-unabridged.rb, line 308
308: def r(s, b, h = {})
309: b, h = h, b if Hash === b
310: @status = s
311: @headers.merge!(h)
312: @body = b
313: end
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
[ show source ]
# File lib/camping-unabridged.rb, line 342
342: def r404(p)
343: P % "#{p} not found"
344: end
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
[ show source ]
# File lib/camping-unabridged.rb, line 361
361: def r500(k,m,e)
362: raise e
363: end
Called if an undefined method is called on a controller, along with the request method m (GET, POST, etc.)
[ show source ]
# File lib/camping-unabridged.rb, line 367
367: def r501(m)
368: P % "#{m.upcase} not implemented"
369: end
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
[ show source ]
# File lib/camping-unabridged.rb, line 329
329: def redirect(*a)
330: r(302,'','Location'=>URL(*a).to_s)
331: end
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
[ show source ]
# File lib/camping-unabridged.rb, line 272
272: def render(v, *a, &b)
273: if t = lookup(v)
274: o = Hash === a[-1] ? a.pop : {}
275: s = (t == true) ? mab{ send(v, *a, &b) } : t.render(self, o[:locals] || {}, &b)
276: s = render(L, o.merge(L => false)) { s } if v.to_s[0] != ?_ && o[L] != false && lookup(L)
277: s
278: else
279: raise "Can't find template #{v}"
280: end
281: end
All requests pass through this method before going to the controller. Some magic in Camping can be performed by overriding this method.
[ show source ]
# File lib/camping-unabridged.rb, line 415
415: def service(*a)
416: r = catch(:halt){send(@method, *a)}
417: @body ||= r
418: self
419: end
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
[ show source ]
# File lib/camping-unabridged.rb, line 382
382: def to_a
383: @env['rack.session'] = Hash[@state]
384: r = Rack::Response.new(@body, @status, @headers)
385: @cookies.each do |k, v|
386: next if @old_cookies[k] == v
387: v = { :value => v, :path => self / "/" } if String === v
388: r.set_cookie(k, v)
389: end
390: r.to_a
391: end
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
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:
Here’s a few examples:
Index # => / PostN # => /post/(\d+) PageX # => /page/([^/]+) Pages # => /pages
You have these variables which describes the request:
You can change these variables to your needs:
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
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:
So, define your catch-all controllers last.
[ show source ]
# File lib/camping-unabridged.rb, line 545
545: def D(p, m, e)
546: p = '/' if !p || !p[0]
547: r.map { |k|
548: k.urls.map { |x|
549: return (k.method_defined?(m)) ?
550: [k, m, *$~[1..-1]] : [I, 'r501', m] if p =~ /^#{x}\/?$/
551: }
552: }
553: [I, 'r404', p]
554: end
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
[ show source ]
# File lib/camping-unabridged.rb, line 570
570: def M
571: def M #:nodoc:
572: end
573: constants.map { |c|
574: k = const_get(c)
575: k.send :include,C,X,Base,Helpers,Models
576: @r=[k]+r if r-[k]==r
577: k.meta_def(:urls){["/#{c.to_s.scan(/.[^A-Z]*/).map(&N.method(:[]))*'/'}"]}if !k.respond_to?:urls
578: }
579: end
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
[ show source ]
# File lib/camping-unabridged.rb, line 526
526: def R *u
527: r=@r
528: Class.new {
529: meta_def(:urls){u}
530: meta_def(:inherited){|x|r<<x}
531: }
532: end
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.
Gets or sets keys in the hash.
@cookies.my_favorite = :macadamian @cookies.my_favorite => :macadamian
[ show source ]
# File lib/camping-unabridged.rb, line 80
80: def method_missing(m,*a)
81: m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super
82: end
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.
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
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
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"
[ show source ]
# File lib/camping-unabridged.rb, line 201
201: def /(p); p[0] == ?/ ? @root + p : p end
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.
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>
[ show source ]
# File lib/camping-unabridged.rb, line 183
183: def R(c,*g)
184: p,h=/\(.+?\)/,g.grep(Hash)
185: g-=h
186: raise "bad route" unless u = c.urls.find{|x|
187: break x if x.scan(p).size == g.size &&
188: /^#{x}\/?$/ =~ (x=g.inject(x){|x,a|
189: x.sub p,U.escape((a.to_param rescue a))}.gsub(/\\(.)/){$1})
190: }
191: h.any?? u+"?"+U.build_query(h[0]) : u
192: end
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.
[ show source ]
# File lib/camping-unabridged.rb, line 224
224: def URL c='/',*a
225: c = R(c, *a) if c.respond_to? :urls
226: c = self/c
227: c = @request.url[/.{8,}?(?=\/)/]+c if c[0]==?/
228: URI(c)
229: end
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
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.
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"
[ show source ]
# File lib/camping/ar.rb, line 66
66: def Base.table_name_prefix
67: "#{name[/\w+/]}_".downcase.sub(/^(#{A}|camping)_/i,'')
68: end
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.
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.
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.
[ show source ]
# File lib/camping/reloader.rb, line 135
135: def initialize(*scripts)
136: @scripts = []
137: @apps = {}
138: update(*scripts)
139: end
Removes all the scripts from the reloader.
[ show source ]
# File lib/camping/reloader.rb, line 162
162: def clear
163: @scripts = []
164: @apps = {}
165: end
[ show source ]
# File lib/camping/reloader.rb, line 141
141: def on_reload(&blk)
142: @callback = blk
143: end
Simply calls reload! on all the Script objects.
[ show source ]
# File lib/camping/reloader.rb, line 177
177: def reload!
178: @apps = {}
179: @scripts.each do |script|
180: script.reload!
181: @apps.update(script.apps)
182: end
183: end
Returns the script which provided the given app.
[ show source ]
# File lib/camping/reloader.rb, line 168
168: def script(app)
169: @scripts.each do |script|
170: return script if script.apps.values.include?(app)
171: end
172:
173: false
174: end
Updates the reloader to only use the scripts provided:
reloader.update("examples/blog.rb", "examples/wiki.rb")
[ show source ]
# File lib/camping/reloader.rb, line 148
148: def update(*scripts)
149: old_scripts = @scripts.dup
150: clear
151:
152: @scripts = scripts.map do |script|
153: file = File.expand_path(script)
154: old_scripts.detect { |s| s.file == file } or
155: Script.new(script, @callback)
156: end
157:
158: reload!
159: end
[ show source ]
# File lib/camping/server.rb, line 98
98: def initialize(*)
99: super
100: @reloader = Camping::Reloader.new
101: @reloader.on_reload do |app|
102: if !app.options.has_key?(:dynamic_templates)
103: app.options[:dynamic_templates] = true
104: end
105:
106: if !Camping::Models.autoload?(:Base) && options[:database]
107: Camping::Models::Base.establish_connection(
108: :adapter => 'sqlite3',
109: :database => options[:database]
110: )
111: end
112: end
113: end
[ show source ]
# File lib/camping/server.rb, line 164
164: def app
165: self
166: end
[ show source ]
# File lib/camping/server.rb, line 168
168: def call(env)
169: reload!
170: apps = @reloader.apps
171:
172: case apps.length
173: when 0
174: index_page(apps)
175: when 1
176: apps.values.first.call(env)
177: else
178: apps.each do |name, app|
179: mount = name.to_s.downcase
180: case env["PATH_INFO"]
181: when %r{^/#{mount}}
182: env["SCRIPT_NAME"] = env["SCRIPT_NAME"] + $&
183: env["PATH_INFO"] = $'
184: return app.call(env)
185: when %r{^/code/#{mount}}
186: return [200, {'Content-Type' => 'text/plain', 'X-Sendfile' => @reloader.script(app).file}, []]
187: end
188: end
189:
190: index_page(apps)
191: end
192: end
[ show source ]
# File lib/camping/server.rb, line 119
119: def default_options
120: super.merge({
121: :Port => 3301,
122: :database => Options::DB
123: })
124: end
[ show source ]
# File lib/camping/server.rb, line 148
148: def find_scripts
149: scripts = options[:scripts].map do |path|
150: if File.file?(path)
151: path
152: elsif File.directory?(path)
153: Dir[File.join(path, '*.rb')]
154: end
155: end.flatten.compact
156:
157: @reloader.update(*scripts)
158: end
[ show source ]
# File lib/camping/server.rb, line 194
194: def index_page(apps)
195: [200, {'Content-Type' => 'text/html'}, [TEMPLATE.result(binding)]]
196: end
[ show source ]
# File lib/camping/server.rb, line 126
126: def middleware
127: h = super
128: h["development"].unshift [XSendfile]
129: h
130: end
[ show source ]
# File lib/camping/server.rb, line 115
115: def opt_parser
116: Options.new
117: end
[ show source ]
# File lib/camping/server.rb, line 160
160: def reload!
161: find_scripts
162: end
[ show source ]
# File lib/camping/server.rb, line 132
132: def start
133: if options[:server] == "console"
134: puts "** Starting console"
135: reload!
136: this = self
137: eval("self", TOPLEVEL_BINDING).meta_def(:reload!) { this.reload!; nil }
138: ARGV.clear
139: IRB.start
140: exit
141: else
142: name = server.name[/\w+$/]
143: puts "** Starting #{name} on #{options[:Host]}:#{options[:Port]}"
144: super
145: end
146: end
[ show source ]
# File lib/camping/server.rb, line 42
42: def parse!(args)
43: args = args.dup
44: options = {}
45:
46: opt_parser = OptionParser.new("", 24, ' ') do |opts|
47: opts.banner = "Usage: camping app1.rb app2.rb..."
48: opts.define_head "#{File.basename($0)}, the microframework ON-button for ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
49: opts.separator ""
50: opts.separator "Specific options:"
51:
52: opts.on("-h", "--host HOSTNAME",
53: "Host for web server to bind to (default is all IPs)") { |v| options[:Host] = v }
54:
55: opts.on("-p", "--port NUM",
56: "Port for web server (defaults to 3301)") { |v| options[:Port] = v }
57:
58: db = DB.sub(HOME, '~/') if DB
59: opts.on("-d", "--database FILE",
60: "SQLite3 database path (defaults to #{db ? db : '<none>'})") { |db_path| options[:database] = db_path }
61:
62: opts.on("-C", "--console",
63: "Run in console mode with IRB") { options[:server] = "console" }
64:
65: server_list = ["mongrel", "webrick", "console"]
66: opts.on("-s", "--server NAME",
67: "Server to force (#{server_list.join(', ')})") { |v| options[:server] = v }
68:
69: opts.separator ""
70: opts.separator "Common options:"
71:
72: # No argument, shows at tail. This will print an options summary.
73: # Try it and see!
74: opts.on_tail("-?", "--help", "Show this message") do
75: puts opts
76: exit
77: end
78:
79: # Another typical switch to print the version.
80: opts.on_tail("-v", "--version", "Show version") do
81: puts Gem.loaded_specs['camping'].version
82: exit
83: end
84: end
85:
86: opt_parser.parse!(args)
87:
88: if args.empty?
89: puts opt_parser
90: exit
91: end
92:
93: options[:scripts] = args
94: options
95: end
[ show source ]
# File lib/camping/server.rb, line 238
238: def initialize(app)
239: @app = app
240: end
[ show source ]
# File lib/camping/server.rb, line 242
242: def call(env)
243: status, headers, body = @app.call(env)
244:
245: if key = headers.keys.grep(/X-Sendfile/i).first
246: filename = headers[key]
247: content = open(filename,'rb') { | io | io.read}
248: headers['Content-Length'] = size(content).to_s
249: body = [content]
250: end
251:
252: return status, headers, body
253: end
[ show source ]
# File lib/camping/server.rb, line 256
256: def size(str)
257: str.bytesize
258: end
[ show source ]
# File lib/camping/server.rb, line 260
260: def size(str)
261: str.size
262: end
To get sessions working for your application:
require 'camping/session' # 1
module Nuts
set :secret, "Oh yeah!" # 2
include Camping::Session # 3
end
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
[ show source ]
# File lib/camping/session.rb, line 28
28: def self.included(app)
29: key = "#{app}.state".downcase
30: secret = app.options[:secret] || [__FILE__, File.mtime(__FILE__)].join(":")
31:
32: app.use Rack::Session::Cookie, :key => key, :secret => secret
33: end
Views is an empty module for storing methods which create HTML. The HTML is described using the Markaby language.
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).
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