class EBuilder

Attributes

controllers[R]
mounted_controllers[R]

Public Class Methods

call(env) click to toggle source
# File lib/e-builder/base.rb, line 5
def self.call env
  new(:automount).call(env)
end
new(automount = false, &proc) click to toggle source

creates new Espresso app.

@param automount if set to any positive value(except Class, Module or Regexp),

all found controllers will be mounted,
if set to a Class, Module or Regexp,
only controllers under given namespace will be mounted.

@param [Proc] proc if block given, it will be executed inside newly created app

# File lib/e-builder/base.rb, line 19
def initialize automount = false, &proc
  @controllers, @subcontrollers = {}, []
  @routes, @hosts, @controllers_hosts = {}, {}, {}
  @automount = automount
  proc && self.instance_exec(&proc)
  use ExtendedRack
  compiler_pool(Hash.new)
end

Public Instance Methods

app() click to toggle source
# File lib/e-builder/base.rb, line 157
def app
  @app ||= begin
    on_boot!
    mount_controllers!
    @sorted_routes = sorted_routes.freeze
    @routes.freeze
    middleware.reverse.inject(lambda {|env| call!(env)}) {|a,e| e[a]}
  end
end
app_root(path = nil)
Alias for: root
auth(opts = {})
Alias for: basic_auth
automount!() click to toggle source

auto-mount auto-discovered controllers. call this only after all controllers defined and app ready to start! leaving it in public zone for better control over mounting.

# File lib/e-builder/base.rb, line 67
def automount!
  controllers = [Class, Module, Regexp].include?(@automount.class) ?
    extract_controllers(@automount) :
    discover_controllers
  mount controllers.select {|c| c.accept_automount?}
end
base_url() click to toggle source
# File lib/e-builder/setup.rb, line 10
def base_url
  @base_url || ''
end
basic_auth(opts = {}) click to toggle source

set authorization at app level. any controller/action will be protected.

# File lib/e-builder/setup.rb, line 86
def basic_auth opts = {}, &proc
  use Rack::Auth::Basic, opts[:realm] || 'AccessRestricted', &proc
end
Also aliased as: auth
boot!()
Alias for: to_app
call(env) click to toggle source
# File lib/e-builder/base.rb, line 153
def call env
  app.call env
end
clear_compiler(*keys) click to toggle source

same as clear_compiler! except it work only on current process

# File lib/e-more/view/e-builder.rb, line 33
def clear_compiler *keys
  compiler_pool && compiler_pool.clear
end
clear_compiler!() click to toggle source

clearing compiler cache

# File lib/e-more/view/e-builder.rb, line 27
def clear_compiler!
  clear_compiler
  ipcm_trigger :clear_compiler
end
compiler_pool(pool = nil) click to toggle source

for most apps, most expensive operations are fs operations and template compilation. to avoid these operations, templates are compiled and stored into memory. on consequent requests they are just rendered.

by default, compiler are disabled. to enable it, set compiler pool at app level.

@example

class App < E
  # ...
end
app = E.new
app.compiler_pool Hash.new # will store compiler cache into a hash
app.run

if you want to use a custom pool, make sure your pool behaves just like a Hash, meant it responds to ‘[]=`, `[]`, and `clear` methods.

# File lib/e-more/view/e-builder.rb, line 20
def compiler_pool  pool =  nil
  @compiler_pool = pool if pool
  @compiler_pool
end
controllers_setup(&proc)
Alias for: global_setup
digest_auth(opts = {}) click to toggle source

(see basic_auth)

# File lib/e-builder/setup.rb, line 92
def digest_auth opts = {}, &proc
  opts[:realm]  ||= 'AccessRestricted'
  opts[:opaque] ||= opts[:realm]
  use Rack::Auth::Digest::MD5, opts, &proc
end
environment() click to toggle source
# File lib/e-builder/base.rb, line 109
def environment
  ENV[ENV__RACK_ENV] || :development
end
global_setup(&proc) click to toggle source

proc given here will be executed inside all controllers. used to setup multiple controllers at once.

# File lib/e-builder/base.rb, line 76
def global_setup &proc
  @global_setup = proc
  self
end
map(url, opts = {}) click to toggle source

set base URL to be prepended to all controllers

# File lib/e-builder/setup.rb, line 4
def map url, opts = {}
  url.is_a?(Hash) && (opts = url) && (url = '')
  @base_url = rootify_url(url).freeze
  @hosts    = extract_hosts(opts)
end
middleware() click to toggle source
# File lib/e-builder/setup.rb, line 144
def middleware
  @middleware ||= []
end
mount(*args, &setup) click to toggle source

mount given/discovered controllers into current app. any number of arguments accepted. String arguments are treated as roots/canonicals. any other arguments are used to discover controllers. controllers can be passed directly or as a Module that contain controllers or as a Regexp matching controller’s name.

proc given here will be executed inside given/discovered controllers

# File lib/e-builder/base.rb, line 37
def mount *args, &setup
  root, controllers, applications = nil, [], []
  opts = args.last.is_a?(Hash) ? args.pop : {}
  args.flatten.each do |a|
    if a.is_a?(String)
      root = rootify_url(a)
    elsif is_app?(a)
      controllers << a
    elsif a.respond_to?(:call)
      applications << a
    else
      controllers.concat extract_controllers(a)
    end
  end
  controllers.each do |c|
    @controllers[c] = [root, opts, setup]
    c.subcontrollers.each do |sc|
      mount(sc, root.to_s + c.base_url, opts)
      @subcontrollers << sc
    end
  end
  
  mount_applications applications, root, opts

  self
end
on_boot(&proc) click to toggle source

block(s) to run just before application starts

# File lib/e-builder/setup.rb, line 234
def on_boot &proc
  (@on_boot ||= []).push(proc)
end
rewrite(rule, &proc) click to toggle source

declaring rewrite rules.

first argument should be a regex and a proc should be provided.

the regex(actual rule) will be compared against Request-URI, i.e. current URL without query string. if some rule depend on query string, use ‘params` inside proc to determine either some param was or not set.

the proc will decide how to operate when rule matched. you can do: ‘redirect(’location’)‘

redirect to new location using 302 status code

‘permanent_redirect(’location’)‘

redirect to new location using 301 status code

‘pass(controller, action, any, params, with => opts)`

pass control to given controller and action without redirect.
consequent params are used to build URL to be sent to given controller.

‘halt(status|body|headers|response)`

send response to browser without redirect.
accepts an arbitrary number of arguments.
if arg is an Integer, it will be used as status code.
if arg is a Hash, it is treated as headers.
if it is an array, it is treated as Rack response and are sent immediately, ignoring other args.
any other args are treated as body.

@note any method available to controller instance are also available inside rule proc.

so you can fine tune the behavior of any rule.
ex. redirect on GET requests and pass control on POST requests.
or do permanent redirect for robots and simple redirect for browsers etc.

@example

app = E.new

# redirect to new address
app.rewrite /\A\/(.*)\.php$/ do |title|
  redirect Controller.route(:index, title)
end

# permanent redirect
app.rewrite /\A\/news\/([\w|\d]+)\-(\d+)\.html/ do |title, id|
  permanent_redirect Forum, :posts, :title => title, :id => id
end

# no redirect, just pass control to News controller
app.rewrite /\A\/latest\/(.*)\.html/ do |title|
  pass News, :index, :scope => :latest, :title => title
end

# Return arbitrary body, status-code, headers, without redirect:
# If argument is a hash, it is added to headers.
# If argument is a Integer, it is treated as Status-Code.
# Any other arguments are treated as body.
app.rewrite /\A\/archived\/(.*)\.html/ do |title|
  if page = Model::Page.first(:url => title)
    halt page.content, 'Last-Modified' => page.last_modified.to_rfc2822
  else
    halt 404, 'page not found'
  end
end

app.run
# File lib/e-builder/setup.rb, line 211
def rewrite rule, &proc
  proc || raise(ArgumentError, "Rewrite rules requires a block to run")
  @routes[rule] = {'GET' => {:rewriter => proc}}
end
Also aliased as: rewrite_rule
rewrite_rule(rule, &proc)
Alias for: rewrite
root(path = nil) click to toggle source

set/get app root

# File lib/e-builder/setup.rb, line 15
def root path = nil
  @root = ('%s/' % path).sub(/\/+\Z/, '/').freeze if path
  @root ||= (::Dir.pwd << '/').freeze
end
Also aliased as: app_root
run(opts = {}) { |server| ... } click to toggle source

by default, Espresso will use WEBrick server. pass :server option and any option accepted by selected(or default) server:

@example use Thin server on its default port

app.run :server => :Thin

@example use EventedMongrel server with custom options

app.run :server => :EventedMongrel, :port => 9090, :num_processors => 1000

@param [Hash] opts @option opts [Symbol] :server (:WEBrick) web server @option opts [Integer] :port (5252) @option opts [String] :host (0.0.0.0)

# File lib/e-builder/base.rb, line 126
def run opts = {}
  boot!

  handler = opts.delete(:server)
  (handler && Rack::Handler.const_defined?(handler)) || (handler = HTTP__DEFAULT_SERVER)

  port = opts.delete(:port)
  opts[:Port] ||= port || HTTP__DEFAULT_PORT

  host = opts.delete(:host) || opts.delete(:bind)
  opts[:Host] = host if host

  $stderr.puts "\n--- Starting Espresso for %s on %s port backed by %s server ---\n\n" % [
    environment, opts[:Port], handler
  ]
  Rack::Handler.const_get(handler).run app, opts do |server|
    %w[INT TERM].each do |sig|
      Signal.trap(sig) do
        $stderr.puts "\n--- Stopping Espresso... ---\n\n"
        server.respond_to?(:stop!) ? server.stop! : server.stop
      end
    end
    server.threaded = opts[:threaded] if server.respond_to? :threaded=
    yield server if block_given?
  end
end
session(use, *args) click to toggle source

allow app to use sessions.

@example keep sessions in memory

class App < E
  # ...
end
app = E.new
app.session :memory
app.run

@example keep sessions in memory using custom options

class App < E
  # ...
end
app = E.new
app.session :memory, :domain => 'foo.com', :expire_after => 2592000
app.run

@example keep sessions in cookies

class App < E
  # ...
end
app = E.new
app.session :cookies
app.run

@example keep sessions in memcache

class App < E
  # ...
end
app = E.new
app.session :memcache
app.run

@example use a custom pool, i.e. github.com/migrs/rack-session-mongo

#> gem install rack-session-mongo

class App < E
  # ...
end

require 'rack/session/mongo'

app = E.new
app.session Rack::Session::Mongo
app.run

@param [Symbol, Class] use @param [Array] args

# File lib/e-builder/setup.rb, line 70
def session use, *args
  args.unshift case use
                 when :memory
                   Rack::Session::Pool
                 when :cookies
                   Rack::Session::Cookie
                 when :memcache
                   Rack::Session::Memcache
                 else
                   use
               end
  use(*args)
end
setup(&proc)
Alias for: global_setup
setup_controllers(&proc)
Alias for: global_setup
streaming_backend(backend = nil) click to toggle source

by default Espresso will use EventMachine for streaming, but it also supports Celluloid, when Reel web-server used. use this method to set Celluloid as streaming backend.

@example

app = E.new do
  streaming_backend :Celluloid
end
# File lib/e-builder/setup.rb, line 226
def streaming_backend backend = nil
  @streaming_backend = backend
  def streaming_backend
    @streaming_backend
  end
end
to_app() click to toggle source
# File lib/e-builder/base.rb, line 167
def to_app
  app
  self
end
Also aliased as: to_app!, boot!
to_app!()
Alias for: to_app
url_map(opts = {}) click to toggle source

displays URLs the app will respond to, with controller and action that serving each URL.

# File lib/e-builder/base.rb, line 86
def url_map opts = {}
  mount_controllers!
  map = sorted_routes.inject({}) do |m,r|
    @routes[r].each_pair {|rm,rs| (m[r] ||= {})[rm] = rs.dup}; m
  end

  def map.to_s
    out = ''
    self.each_pair do |route, request_methods|
      next if route.source.size == 0
      out << "%s\n" % route.source
      request_methods.each_pair do |rm,rs|
        out << "  %s%s" % [rm, ' ' * (10 - rm.to_s.size)]
        out << "%s\n" % (rs[:app] || [rs[:controller], rs[:action]]*'#')
      end
      out << "\n"
    end
    out
  end
  map
end
Also aliased as: urlmap
urlmap(opts = {})
Alias for: url_map
use(ware, *args, &block) click to toggle source

middleware declared here will be used on all controllers.

especially, here should go middleware that changes app state, which wont work if defined inside controller.

you can of course define any type of middleware at app level, it is even recommended to do so to avoid redundant middleware declaration at controllers level.

@example

class App < E
  # ...
end
app = E.new
app.use SomeMiddleware, :with, :some => :opts
app.run

Any middleware that does not change app state, i.e. non-upfront middleware, can be defined inside controllers.

@note middleware defined inside some controller will run only for that controller.

to have global middleware, define it at app level.

@example defining middleware at app level

module App
  class Forum < E
    map '/forum'
    # ...
  end

  class Blog < E
    map '/blog'
    # ...
  end
end

app = E.new
app.use Rack::CommonLogger
app.use Rack::ShowExceptions
app.run
# File lib/e-builder/setup.rb, line 140
def use ware, *args, &block
  middleware << proc { |app| ware.new(app, *args, &block) }
end

Private Instance Methods

call!(env) click to toggle source
# File lib/e-builder/base.rb, line 175
def call! env
  path = env[ENV__PATH_INFO]
  script_name = env[ENV__SCRIPT_NAME]
  @sorted_routes.each do |route|
    if matches = route.match(path)

      if route_setup = @routes[route][env[ENV__REQUEST_METHOD]] || @routes[route][:*]

        if route_setup[:rewriter]
          break unless valid_host?(@hosts.merge(@controllers_hosts), env)
          app = ERewriter.new(*matches.captures, &route_setup[:rewriter])
          return app.call(env)
        elsif route_setup[:app]
          break unless valid_host?(@hosts.merge(@controllers_hosts), env)
          env[ENV__PATH_INFO] = normalize_path(matches[1].to_s)
          return route_setup[:app].call(env)
        else
          break unless valid_host?(@hosts.merge(route_setup[:controller].hosts), env)

          env[ENV__SCRIPT_NAME] = route_setup[:path].freeze
          env[ENV__PATH_INFO]   = normalize_path(matches[1].to_s)

          if format_regexp = route_setup[:format_regexp]
            env[ENV__ESPRESSO_PATH_INFO], env[ENV__ESPRESSO_FORMAT] = \
              env[ENV__PATH_INFO].split(format_regexp)
          end

          controller_instance = route_setup[:controller].new
          controller_instance.action_setup = route_setup
          app = Rack::Builder.new
          app.run controller_instance
          route_setup[:controller].middleware.each {|w,a,p| app.use w, *a, &p}
          return app.call(env)
        end
      else
        return [
          STATUS__NOT_IMPLEMENTED,
          {"Content-Type" => "text/plain"},
          ["Resource found but it can be accessed only through %s" % @routes[route].keys.join(", ")]
        ]
      end
    end
  end
  [
    STATUS__NOT_FOUND,
    {'Content-Type' => "text/plain", "X-Cascade" => "pass"},
    ['Not Found: %s' % env[ENV__PATH_INFO]]
  ]
ensure
  env[ENV__PATH_INFO] = path
  env[ENV__SCRIPT_NAME] = script_name
end
discover_controllers(namespace = nil) click to toggle source
# File lib/e-builder/base.rb, line 280
def discover_controllers namespace = nil
  controllers = ObjectSpace.each_object(Class).
    select { |c| is_app?(c) }.reject { |c| [E].include? c }
  namespace.is_a?(Regexp) ?
    controllers.select { |c| c.name =~ namespace } :
    controllers
end
Also aliased as: discovered_controllers
discovered_controllers(namespace = nil)
extract_controllers(namespace) click to toggle source
# File lib/e-builder/base.rb, line 289
def extract_controllers namespace
  if [Class, Module].include?(namespace.class)
    return discover_controllers.select {|c| c.name =~ /\A#{namespace}/}
  end
  discover_controllers namespace
end
mount_application(applications, root = nil, opts = {})
Alias for: mount_applications
mount_applications(applications, root = nil, opts = {}) click to toggle source
# File lib/e-builder/base.rb, line 296
def mount_applications applications, root = nil, opts = {}
  applications = [applications] unless applications.is_a?(Array)
  applications.compact!
  return if applications.empty?
  root.is_a?(Hash) && (opts = root) && (root = nil)

  request_methods = (opts[:on] || opts[:request_method] || opts[:request_methods])
  request_methods = [request_methods] unless request_methods.is_a?(Array)
  request_methods.compact!
  request_methods.map! {|m| m.to_s.upcase}.reject! do |m|
    HTTP__REQUEST_METHODS.none? {|lm| lm == m}
  end
  request_methods = HTTP__REQUEST_METHODS if request_methods.empty?

  route = route_to_regexp(rootify_url(root || '/'))
  applications.each do |a|
    @routes[route] = request_methods.inject({}) {|map,m| map.merge(m => {app: a})}
  end
end
Also aliased as: mount_application
mount_controller(controller, root = nil, opts = {}) click to toggle source
# File lib/e-builder/base.rb, line 258
def mount_controller controller, root = nil, opts = {}, &setup
  return if @mounted_controllers.include?(controller)
  root.is_a?(Hash) && (opts = root) && (root = nil)

  if root || base_url.size > 0
    controller.remap!(base_url + root.to_s, opts)
  end

  unless @subcontrollers.include?(controller)
    @global_setup && controller.global_setup!(&@global_setup)
    setup && controller.external_setup!(&setup)
  end

  controller.mount! self

  @routes.update controller.routes
  @controllers_hosts.update controller.hosts
  controller.rewrite_rules.each {|(rule,proc)| rewrite_rule(rule, &proc)}

  @mounted_controllers << controller
end
mount_controllers!() click to toggle source
# File lib/e-builder/base.rb, line 252
def mount_controllers!
  automount! if @automount
  @mounted_controllers = []
  @controllers.each_pair {|c,(root,opts,setup)| mount_controller(c, root, opts, &setup)}
end
normalize_path(path) click to toggle source
# File lib/e-builder/base.rb, line 241
def normalize_path path
  (path_ok?(path) ? path : '/' << path).freeze
end
on_boot!() click to toggle source

execute blocks defined via ‘on_boot`

# File lib/e-builder/base.rb, line 318
def on_boot!
  (@on_boot || []).each {|b| b.call}
end
path_ok?(path) click to toggle source

checking whether path is empty or starts with a slash

# File lib/e-builder/base.rb, line 246
def path_ok? path
  # comparing fixnums are much faster than comparing strings
  path.hash == (@empty_string_hash ||= '' .hash) || # faster than path.empty?
    path[0].hash == (@slash_hash   ||= '/'.hash)    # faster than path =~ /^\//
end
sorted_routes() click to toggle source
# File lib/e-builder/base.rb, line 228
def sorted_routes
  @routes.keys.sort {|a,b| b.source.size <=> a.source.size}
end
valid_host?(accepted_hosts, env) click to toggle source
# File lib/e-builder/base.rb, line 232
def valid_host? accepted_hosts, env
  http_host, server_name, server_port =
    env.values_at(ENV__HTTP_HOST, ENV__SERVER_NAME, ENV__SERVER_PORT)
  accepted_hosts[http_host] ||
    accepted_hosts[server_name] ||
    http_host == server_name ||
    http_host == server_name+':'+server_port
end