class E

Attributes

action_setup[RW]

Public Class Methods

[](action_or_action_with_format) click to toggle source

@example

class Forum < E
  format '.html', '.xml'

  def posts
  end
end

App[:posts]             #=> /forum/posts
App['posts.html']       #=> /forum/posts.html
App['posts.xml']        #=> /forum/posts.xml
App['posts.json']       #=> nil
# File lib/e-core/controller/setups_reader.rb, line 47
def [] action_or_action_with_format
  mounted? || raise("`[]' method works only on mounted controllers")
  @__e__route_by_action[action_or_action_with_format] ||
    @__e__route_by_action_with_format[action_or_action_with_format]
end
accept_automount?() click to toggle source
# File lib/e-core/controller/mounter.rb, line 42
def accept_automount?
  true unless @reject_automount
end
action_setup() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 6
def action_setup;  @__e__action_setup end
after(*matchers, &proc) click to toggle source

(see before)

# File lib/e-core/controller/setups_writer.rb, line 242
def after *matchers, &proc
  add_setup :z, *matchers, &proc
end
alias_action(url, action) click to toggle source

allow to set routes directly, without relying on path rules.

@example make :bar method to serve /bar, /some/url and /some/another/url

def bar
  # ...
end

alias_action 'some/url', :bar
alias_action 'some/another/url', :bar

@note private and protected methods usually are not publicly available via HTTP.

however, if you add an action alias to such a method, it becomes public via its alias.
to alias a private/protected method and keep it private,
use standard ruby `alias` or `alias_method` rather than `alias_action`

@param [String] url target URL @param [Symbol] action

# File lib/e-core/controller/setups_writer.rb, line 68
def alias_action url, action
  return if mounted?
  ((@__e__alias_actions ||= {})[action]||=[]) << url
end
alias_actions() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 60
def alias_actions
  @__e__alias_actions || {}
end
alias_after(action, *others) click to toggle source

(see alias_before)

# File lib/e-core/controller/setups_writer.rb, line 247
def alias_after action, *others
  return if mounted?
  (@__e__after_aliases ||= {})[action] = others
end
alias_before(action, *others) click to toggle source

allow to run multiple callbacks for same action. action will run its callback(s)(if any) regardless aliased callbacks. callbacks will run in the order was added, that’s it, if an aliased callback defined before action’s callback, it will run first.

@example run :save callback on :post_crud action

class App < E
  before :save do
    # ...
  end

  def post_crud
    # ...
  end
  alias_before :post_crud, :save
end

@param [Symbol] action @param [Array] others callbacks to run before given actions, beside its own callback

# File lib/e-core/controller/setups_writer.rb, line 236
def alias_before action, *others
  return if mounted?
  (@__e__before_aliases ||= {})[action] = others
end
app() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 3
def app;           @__e__app    end
around(*matchers, &proc) click to toggle source

use ‘around` when you need some action to run inside given block. call `invoke_action` where you need action to be executed.

@example graciously throw an error if some /remote/ action takes more than 5 seconds to run

class App < E

  around /remote/ do

    Timeout.timeout(5) do
      begin
        invoke_action # executing action
      rescue => e
        fail 500, e.message
      end
    end

  end

  def remote_init
    # occasionally slow action
  end

  def remote_post
    # occasionally slow action
  end

  def remote_fetch
    # occasionally slow action
  end
end
# File lib/e-core/controller/setups_writer.rb, line 283
def around *matchers, &proc
  add_setup :around, *matchers, &proc
end
base_url() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 21
def base_url
  @__e__base_url || default_route
end
Also aliased as: baseurl
baseurl()
Alias for: base_url
before(*matchers, &proc) click to toggle source

add setups to be executed before/after given(or all) actions.

@note setups will be executed in the order they was added @note before, setup and on are aliases

@example setup to be executed before any action

setup do
  # ...
end

@example defining the setup to be executed only before :index

before :index do
   # ...
end

@example defining a setup to be executed after :post_login and :put_edit actions

after :post_login, :put_edit do
  # ...
end

@example running a setup before :blue action

     as well as before actions matching "red"
before :blue, /red/ do
  # ...
end

@example running a setup for any action on .json format

on '.json' do
  # ...
end

@example running a setup for :api action on .json format

on 'api.json' do
  # ...
end
# File lib/e-core/controller/setups_writer.rb, line 205
def before *matchers, &proc
  add_setup :a, *matchers, &proc
end
Also aliased as: setup, on
call(env) click to toggle source
# File lib/e-core/controller/mounter.rb, line 7
def call env
  mount.call(env)
end
canonicals() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 26
def canonicals
  @__e__canonicals || []
end
default_route() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 30
def default_route
  @__e__default_route ||= EUtils.class_to_route(self.name).freeze
end
define_setup_method(meth) click to toggle source
# File lib/e-core/e-core.rb, line 47
def define_setup_method meth
  (class << self; self end).class_exec do
    define_method meth do |*args, &proc|
      add_setup(:a) { self.send meth, *args, &proc }
    end
  end
end
disable_format_for(*matchers) click to toggle source

allow to disable format for specific action(s). any number of arguments accepted(zero arguments will have no effect).

@example all actions will serve .xml format,

        except :read action, which wont serve any format

format '.xml'
disable_format_for :read

@example actions matching /api/ wont serve any formats

disable_format_for /api/
# File lib/e-core/controller/setups_writer.rb, line 164
def disable_format_for *matchers
  return if mounted?
  (@__e__disable_formats_for ||= []).concat matchers
end
error(*error_codes, &proc) click to toggle source

define a block to be executed on errors. the block should return a [String] error message.

multiple error codes accepted. if no error codes given, the block will be effective for any error type.

@example handle 404 errors:

class App < E

  error 404 do |error_message|
    "Some weird error occurred: #{ error_message }"
  end
end

@param [Integer] code @param [Proc] proc

# File lib/e-core/controller/setups_writer.rb, line 311
def error *error_codes, &proc
  return if mounted?
  proc || raise(ArgumentError, 'Error handlers require a block')
  error_codes.any? || error_codes = [:*]
  meth = proc_to_method(:error_handlers, *error_codes, &proc)
  error_codes.each {|c| (@__e__error_handlers ||= {})[c] = [meth, proc.arity]}
end
error_handler(error_code) click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 68
def error_handler error_code
  ((@__e__error_handlers || {}).find {|k,v| error_code == k} || []).last
end
external_setup!(&setup) click to toggle source
# File lib/e-core/controller/mounter.rb, line 73
def external_setup! &setup
  @__e__setup_container = :external
  self.class_exec &setup
  @__e__setup_container = nil
end
format(*formats) click to toggle source

automatically setting URL extension and Content-Type. this method will set formats for all actions.

@example

class App < E

  format '.html', '.xml', '.etc'

end
# File lib/e-core/controller/setups_writer.rb, line 103
def format *formats
  return if mounted?
  (@__e__formats ||= []).concat formats
end
format_for(matcher, *formats) click to toggle source

setting format(s) for specific action. first argument is the action name or a Regex matching multiple action names. consequent arguments are the formats to be served.

@example make :page action to serve .html format

class App < E

  format_for :page, '.html'

end

@example make :page action to serve .html and .xml formats

class App < E

  format_for :page, '.html', '.xml'

end

@example make actions that match /api/ to serve .json format

class App < E

  format_for /api/, '.json'

end

@example make :api action to serve .json and .xml formats

            and any other actions to serve .html format

class App < E

  format_for :api, '.json', '.xml'
  format '.html'

end
# File lib/e-core/controller/setups_writer.rb, line 146
def format_for matcher, *formats
  return if mounted?
  (@__e__formats_for ||= []) << [matcher, formats]
end
formats(action) click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 64
def formats action
  (@__e__expanded_formats || {})[action] || []
end
global_setup!(&setup) click to toggle source
# File lib/e-core/controller/mounter.rb, line 67
def global_setup! &setup
  @__e__setup_container = :global
  self.class_exec self, &setup
  @__e__setup_container = nil
end
hosts() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 5
def hosts;         @__e__hosts || {}  end
import(mdl) click to toggle source

‘import` are used to share actions between controllers

# File lib/e-core/e-core.rb, line 42
def import mdl
  native_include(mdl)
  (@__e__imported_methods ||= []).concat mdl.public_instance_methods(false)
end
include(mdl) click to toggle source
# File lib/e-core/e-core.rb, line 35
def include mdl
  native_include(mdl)
  (@__e__included_methods ||= []).concat mdl.public_instance_methods(false)
end
Also aliased as: native_include
inherited(ctrl) click to toggle source

allow controllers to define ‘initialize` method that will run on clean state, before any Espresso stuff. @note it wont accept any arguments

# File lib/e-core/e-core.rb, line 23
def inherited ctrl
  def ctrl.new *args
    o = allocate
    o.send :initialize # `initialize` is a private method, using `send`
    o.initialize_controller *args
    o
  end
end
map(*args) click to toggle source

setting controller’s base URL

if multiple paths provided, first path is treated as root, and other ones are treated as canonical routes. canonical routes allow controller to serve multiple roots.

also it accepts a Hash of options. if :host option given, controller will respond only to requests originating on given host. multiple hosts can be provided as an Array via :hosts option.

@example respond only to requests from site.com

map host: 'site.com'

@example respond only to requests from site.com and site.net

map hosts: ['site.com', 'site.net']
# File lib/e-core/controller/setups_writer.rb, line 19
def map *args
  return if mounted?
  map! *args
end
mapped?() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 8
def mapped?;       @__e__base_url end
middleware() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 87
def middleware
  @__e__middleware || []
end
mount(*roots, &setup) click to toggle source

used when mounted manually

@param [Array] roots first arg treated as base URL,

consequent ones treated as canonical URLs.

@param [Proc] setup setup block. to be executed at class level

# File lib/e-core/controller/mounter.rb, line 17
def mount *roots, &setup
  @__e__app ||= EBuilder.new.mount(self, *roots, &setup).to_app
end
mount!(app) click to toggle source

used when mounted from an E instance

@param [Object] app EBuilder instance

# File lib/e-core/controller/mounter.rb, line 25
def mount! app
  return if mounted?
  @__e__app = app

  # Important - expand_formats! should run before expand_setups!
  expand_formats!
  expand_setups!
  generate_routes!
  lock!

  @__e__mounted = true
end
mount_controller(*controllers)
Alias for: mount_controllers
mount_controllers(*controllers) click to toggle source

allow to mount sub-controllers under the base URL of parent controller. almost same as ‘import`, except actions will be executed in the sub-controller context.

@note sub-controllers wont be automounted when app built.

though you can still can mount them manually.

@note any controller can be mounted only once,

so do not try to mount same sub-controller into multiple controllers!
# File lib/e-core/controller/setups_writer.rb, line 82
def mount_controllers *controllers
  (@__e__subcontrollers ||= [])
  controllers.each do |c|
    EUtils.is_app?(c) ?
      (c.reject_automount!; (@__e__subcontrollers << c).uniq!) :
      warn('"%s" should be a Espresso controller, skipping mount' % CGI.escape_html(c))
  end
end
Also aliased as: mount_controller
mounted?() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 9
def mounted?;      @__e__mounted || @__e__app end
native_include(mdl)
Alias for: include
new(*args, &proc) click to toggle source

allow to create new applications by ‘E.new`

@example

app = E.new do
  # some setup...
end
require './controllers'
app.mount Something
app.run
# File lib/e-core/e-core.rb, line 16
def new *args, &proc
  EBuilder.new *args, &proc
end
new(controller) click to toggle source
# File lib/e-core/instance/cookies.rb, line 21
def initialize controller
  @controller, @request, @response =
    controller, controller.request, controller.response
end
on(*matchers, &proc)

convenient when doing some setup based on format @example

on '.xml' do
  # ...
end
Alias for: before
path_rule(from, to) click to toggle source

add/update a path rule

@note default rules

- "__"   (2 underscores) => "-" (dash)
- "___"  (3 underscores) => "/" (slash)
- "____" (4 underscores) => "." (period)

@example

path_rule  "!" => ".html"

def some_action!
  # will resolve to some_action.html
end

@example

path_rule  /_j$/ => ".json"

def some_action_j
  # will resolve to some_action.json
end
# File lib/e-core/controller/setups_writer.rb, line 45
def path_rule from, to
  return if mounted?
  from = %r[#{from}] unless from.is_a?(Regexp)
  (@__e__path_rules ||= Hash[EConstants::PATH_RULES]).update from => to
end
path_rules() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 53
def path_rules
  @__e__sorted_path_rules ||= begin
    rules = @__e__path_rules || EConstants::PATH_RULES
    Hash[rules.sort {|a,b| b.first.source.size <=> a.first.source.size}].freeze
  end
end
public_actions() click to toggle source

methods to be translated into HTTP paths. if controller has no methods, defining index with some placeholder text.

@example

class News < E
  map '/news'

  def index
    # ...
  end
  # will serve GET /news/index and GET /news

  def post_index
    # ...
  end
  # will serve POST /news/index and POST /news
end

@example

class Forum < E
  map '/forum'

  def online_users
    # ...
  end
  # will serve GET /forum/online_users

  def post_create_user
    # ...
  end
  # will serve POST /forum/create_user
end

HTTP path params passed to action as arguments. if arguments does not meet requirements, HTTP 404 error returned.

@example

def foo arg1, arg2
end
# /foo/some-arg/some-another-arg        - OK
# /foo/some-arg                         - 404 error

def foo arg, *args
end
# /foo/at-least-one-arg                 - OK
# /foo/one/or/any/number/of/args        - OK
# /foo                                  - 404 error

def foo arg1, arg2 = nil
end
# /foo/some-arg/                        - OK
# /foo/some-arg/some-another-arg        - OK
# /foo/some-arg/some/another-arg        - 404 error
# /foo                                  - 404 error

def foo arg, *args, last
end
# /foo/at-least/two-args                - OK
# /foo/two/or/more/args                 - OK
# /foo/only-one-arg                     - 404 error

def foo *args
end
# /foo                                  - OK
# /foo/any/number/of/args               - OK

def foo *args, arg
end
# /foo/at-least-one-arg                 - OK
# /foo/one/or/more/args                 - OK
# /foo                                  - 404 error

@return [Array]

# File lib/e-core/controller/actions.rb, line 76
def public_actions
  @__e__public_actions ||= begin

    actions = begin
      (self.public_instance_methods(false)) +
      (@__e__alias_actions    || {}).keys   +
      (@__e__imported_methods || [])
    end.uniq - (@__e__included_methods || [])
    
    if actions.empty?
      define_method :index do |*|
        'Get rid of this placeholder by defining %s#index' % self.class
      end
      actions << :index
    end
    actions
  end
end
reject_automount!() click to toggle source

use this when you do not want some controller(s) to be automounted

# File lib/e-core/controller/mounter.rb, line 39
def reject_automount!
  @reject_automount = true
end
remap!(root, opts = {}) click to toggle source

remap served root(s) by prepend given path to controller’s root.

@note Important: all actions should be defined before re-mapping occurring

@example

class Forum < E
  map '/forum', '/forums'
  # ...
end

app = E.new.mount(Forum, '/new-root')
# app will serve:
#   - /new-root/forum
#   - /new-root/forums
# File lib/e-core/controller/mounter.rb, line 61
def remap! root, opts = {}
  return if mounted?
  new_canonicals = canonicals.map {|c| EUtils.rootify_url(root, c)}
  map! EUtils.rootify_url(root, base_url), *new_canonicals.uniq, opts
end
rewrite(rule, &proc) click to toggle source
# File lib/e-core/controller/setups_writer.rb, line 319
def rewrite rule, &proc
  proc || raise(ArgumentError, "Rewrite rules requires a block to run")
  (@__e__rewrite_rules ||= []) << [rule, proc]
end
Also aliased as: rewrite_rule
rewrite_rule(rule, &proc)
Alias for: rewrite
rewrite_rules() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 7
def rewrite_rules; @__e__rewrite_rules || []  end
route(*args) click to toggle source

build URL from given action name(or path) and consequent params @return [String]

# File lib/e-core/controller/setups_reader.rb, line 14
def route *args
  mounted? || raise("`route' works only on mounted controllers. Please consider to use `base_url' instead.")
  return base_url if args.size == 0
  (route = self[args.first]) && args.shift
  EUtils.build_path(route || base_url, *args)
end
routes() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 4
def routes;        @__e__routes end
run(*args) click to toggle source
# File lib/e-core/controller/mounter.rb, line 3
def run *args
  mount.run *args
end
setup(*matchers, &proc)
Alias for: before
setup_aliases(position, action) click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 72
def setup_aliases position, action
  if position == :a
    (@__e__before_aliases || {})[action] || []
  elsif position == :z
    (@__e__after_aliases  || {})[action] || []
  else
    []
  end
end
setups(position, action, format) click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 82
def setups position, action, format
  return [] unless (s = @__e__expanded_setups) && (s = s[position]) && (s = s[action])
  s[format] || []
end
subcontrollers() click to toggle source
# File lib/e-core/controller/setups_reader.rb, line 91
def subcontrollers
  (@__e__subcontrollers || []).uniq
end
use(ware, *args, &proc) click to toggle source

add Rack middleware to chain

# File lib/e-core/controller/setups_writer.rb, line 288
def use ware, *args, &proc
  return if mounted?
  (@__e__middleware ||= []).none? {|w| w.first == ware} && 
    @__e__middleware << [ware, args, proc]
end

Private Class Methods

action_parameters(action) click to toggle source

returning required parameters calculated by arity

# File lib/e-core/controller/actions.rb, line 124
def action_parameters action
  method = self.instance_method(action)
  [method.parameters.freeze, EUtils.method_arity(method).freeze]
end
add_setup(position, *matchers, &proc) click to toggle source
# File lib/e-core/controller/setups_writer.rb, line 327
def add_setup position, *matchers, &proc
  return if mounted?
  (@__e__setups  ||= {})[@__e__setup_container] ||= {}
  method   = proc_to_method(:setups, position, *matchers, &proc)
  matchers = [:*] if matchers.empty?
  matchers.each do |matcher|
    (@__e__setups[@__e__setup_container][position] ||= []) << [matcher, method]
  end
end
define_format_helpers(global_formats, strict_formats) click to toggle source

defining a handy format? method for each format. eg. json? for “.json”, xml? for “.xml” etc. these methods aimed to replace the ‘if format == ’.json’‘ redundancy

@example

class App < E

  format '.json'

  def page
    # on /page, json? will return nil
    # on /page.json, json? will return '.json'
  end
end
# File lib/e-core/controller/mounter.rb, line 327
def define_format_helpers global_formats, strict_formats
  (all_formats = (global_formats + strict_formats.map {|s| s.last}.flatten).uniq)
  (all_formats = Hash[all_formats.zip(all_formats)]).each_key do |f|
    method_name = '%s?' % f.sub('.', '')
    define_method method_name do
      # Hash searching is a lot faster than String comparison
      all_formats[format]
    end
    private method_name
  end
end
expand_formats!() click to toggle source

turning Regexp and * matchers into real action names

# File lib/e-core/controller/mounter.rb, line 286
def expand_formats!
  global_formats = (@__e__formats||[]).map {|f| '.' << f.to_s.sub('.', '')}.uniq
  strict_formats = (@__e__formats_for||[]).inject([]) do |u,(m,f)|
    u << [m, f.map {|e| '.' << e.to_s.sub('.', '')}.uniq]
  end

  define_format_helpers(global_formats, strict_formats)

  @__e__expanded_formats = public_actions.inject({}) do |map, action|
    map[action] = global_formats

    action_formats = strict_formats.inject([]) do |formats,(m,f)|
      m == action ||
        (m.is_a?(Regexp) && action.to_s =~ m) ? formats.concat(f) : formats
    end
    map[action] = action_formats if action_formats.any?

    (@__e__disable_formats_for||[]).each do |m|
      map.delete(action) if m == action || (m.is_a?(Regexp) && action.to_s =~ m)
    end

    map
  end
end
expand_setups!() click to toggle source

avoid regexp operations at runtime by turning Regexp and * matchers into real action names at loadtime. also this will match setups by formats.

any number of arguments accepted. if zero arguments given,

the setup will be effective for all actions.

when an argument is a symbol,

the setup will be effective only for action with same name as given symbol.

when an argument is a Regexp,

the setup will be effective for all actions matching given Regex.

when an argument is a String it is treated as format,

and the setup will be effective only for actions that serve given format.

any other arguments ignored.

@note when passing a format as matcher:

if URL has NO format, format-related setups are excluded.
when URL does contain a format, ALL action-related setups becomes effective.

@note Regexp matchers are used ONLY to match action names,

not formats nor action names with format.
thus, NONE of this will work: /\.(ht|x)ml/, /.*pi\.xml/  etc.

@example

class App < E

  format '.json', '.xml'

  layout :master

  setup 'index.xml' do
    # ...
  end

  setup /api/ do
    # ...
  end

  setup '.json', 'read.xml' do
    # ...
  end

  def index
    # on /index, will use following setups:
    #   - `layout ...` (matched via *)
    #
    # on /index.json, will use following setups:
    #   - `layout ...` (matched via *)
    #   - `setup '.json'...`  (matched via .json)
    #
    # on /index.xml, will use following setups:
    #   - `layout ...` (matched via *)
    #   - `setup 'index.xml'...`  (matched via index.xml)
  end

  def api
    # on /api, will use following setups:
    #   - `layout ...` (matched via *)
    #   - `setup /api/...`  (matched via /api/)
    #
    # on /api.json, will use following setups:
    #   - `layout ...` (matched via *)
    #   - `setup /api/...`  (matched via /api/)
    #   - `setup '.json'...`  (matched via .json)
    #
    # on /api.xml, will use following setups:
    #   - `layout ...` (matched via *)
    #   - `setup /api/...`  (matched via /api/)
  end

  def read
    # on /read, will use following setups:
    #   - `layout ...` (matched via *)
    #
    # on /read.json, will use following setups:
    #   - `layout ...` (matched via *)
    #   - `setup '.json'...`  (matched via .json)
    #
    # on /read.xml, will use following setups:
    #   - `layout ...` (matched via *)
    #   - `setup ... 'read.xml'`  (matched via read.xml)
  end

end
# File lib/e-core/controller/mounter.rb, line 247
def expand_setups!
  setups_map = {}
  [:global, :external, nil].each do |container|
    container_setups = (@__e__setups||{})[container]||{}
    public_actions.each do |action|

      # making sure it will work for both ".format" and "action.format" matchers
      action_formats = formats(action) + formats(action).map {|f| action.to_s + f}

      container_setups.each_pair do |position, setups|

        action_setups = setups.select do |(m)|
          m == :* || m == action ||
            (m.is_a?(Regexp) && action.to_s =~ m) ||
            (m.is_a?(String) && action_formats.include?(m)) ||
            setup_aliases(position, action).include?(m)
        end

        (((setups_map[position]||={})[action]||={})[nil]||=[]).concat action_setups.inject([]) { |f,s|
          # excluding format-related setups
          s.first.is_a?(String) ? f : f << s.last
        }

        formats(action).each do |format|
          (setups_map[position][action][format]||=[]).concat action_setups.inject([]) {|f,s|
            # excluding format-related setups that does not match current format
            s.first.is_a?(String) ?
              (s.first =~ /#{Regexp.escape format}\Z/ ? f << s.last : f) : f << s.last
          }
        end

      end
      
    end
  end
  @__e__expanded_setups = setups_map
end
generate_action_setup(action) click to toggle source
# File lib/e-core/controller/actions.rb, line 96
def generate_action_setup action
  action_name, request_method = EUtils.deRESTify_action(action)
  
  action_path = EUtils.action_to_route(action_name, path_rules).freeze
  path = EUtils.rootify_url(base_url, action_path).freeze

  action_arguments, required_arguments = action_parameters(action)

  format_regexp = if formats(action).any?
    regexp = formats(action).map {|f| Regexp.escape f}.join('|')
    /(?:\A(?:\/{0,})?#{action})?(#{regexp})\Z/
  else
    nil
  end
  {
            controller: self,
                action: action,
           action_name: action_name,
           action_path: action_path,
      action_arguments: action_arguments,
    required_arguments: required_arguments,
                  path: path.freeze,
         format_regexp: format_regexp,
        request_method: request_method,
  }.freeze
end
generate_routes!() click to toggle source
# File lib/e-core/controller/mounter.rb, line 100
def generate_routes!
  reset_routes_data!
  persist_action_setups!
  
  public_actions.each do |action|
    @__e__action_setup[action].each_pair do |request_method, setup|
      set_action_routes(setup)
      set_canonical_routes(setup)
      set_alias_routes(setup)
    end
  end
end
lock!() click to toggle source
# File lib/e-core/controller/mounter.rb, line 88
def lock!
  self.instance_variables.reject {|v| v.to_s == '@__e__app' }.each do |var|
    (var.to_s =~ /@__e__/) && (val = self.instance_variable_get(var)) && val.freeze
  end
end
map!(*args) click to toggle source
# File lib/e-core/controller/mounter.rb, line 81
def map! *args
  opts = args.last.is_a?(Hash) ? args.pop : {}
  @__e__base_url   = EUtils.rootify_url(args.shift.to_s).freeze
  @__e__canonicals = args.map { |p| EUtils.rootify_url(p.to_s) }.freeze
  (@__e__hosts ||= {}).update EUtils.extract_hosts(opts)
end
persist_action_setups!() click to toggle source
# File lib/e-core/controller/mounter.rb, line 113
def persist_action_setups!
  public_actions.each do |action|
    action_setup = generate_action_setup(action)
    action_setup.values_at(:action, :action_name).each do |matcher|
      (@__e__action_setup[matcher] ||= {})[action_setup[:request_method]] = action_setup

      @__e__route_by_action[matcher] = action_setup[:path]
      formats(action).each do |format|
        @__e__route_by_action_with_format[matcher.to_s + format] = action_setup[:path] + format
      end
    end
  end
end
proc_to_method(*chunks, &proc) click to toggle source

instance_exec at runtime is expensive enough, so compiling procs into methods at load time.

# File lib/e-core/controller/setups_writer.rb, line 339
def proc_to_method *chunks, &proc
  chunks += [self.to_s, proc.__id__]
  name = ('__e__%s__' % chunks.join('_').gsub(/[^\w|\d]/, '_')).to_sym
  define_method name, &proc
  private name
  name
end
reset_routes_data!() click to toggle source
# File lib/e-core/controller/mounter.rb, line 94
def reset_routes_data!
  @__e__routes = {}
  @__e__action_setup = {}
  @__e__route_by_action, @__e__route_by_action_with_format = {}, {}
end
set_action_routes(action_setup) click to toggle source
# File lib/e-core/controller/mounter.rb, line 127
def set_action_routes action_setup
  set_route(action_setup[:path], action_setup)
  set_route(base_url, action_setup) if action_setup[:action_name] == EConstants::INDEX_ACTION
end
set_alias_routes(action_setup) click to toggle source
# File lib/e-core/controller/mounter.rb, line 140
def set_alias_routes action_setup
  aliases = alias_actions[action_setup[:action]] || []

  aliases.each do |a|
    a_route = EUtils.rootify_url(base_url, a)
    a_setup = action_setup.merge(:path => a_route)
    set_route(a_route, a_setup)
  end

  canonicals.each do |c|
    aliases.each  do |a|
      a_route = EUtils.rootify_url(c, a)
      a_setup = action_setup.merge(:path => a_route, :canonical => action_setup[:path])
      set_route(a_route, a_setup)
    end
  end
end
set_canonical_routes(action_setup) click to toggle source
# File lib/e-core/controller/mounter.rb, line 132
def set_canonical_routes action_setup
  canonicals.each do |c|
    c_route = EUtils.canonical_to_route(c, action_setup)
    c_setup = action_setup.merge(:path => c_route, :canonical => action_setup[:path])
    set_route(c_route, c_setup)
  end
end
set_route(route, setup) click to toggle source
# File lib/e-core/controller/mounter.rb, line 158
def set_route route, setup
  regexp = EUtils.route_to_regexp(route)
  (@__e__routes[regexp] ||= {})[setup[:request_method]] = setup
end

Public Instance Methods

[](action) click to toggle source
# File lib/e-core/instance/base.rb, line 183
def [] action
  self.class[action]
end
[]=(key, val) click to toggle source

set cookie header

@param [String, Symbol] key @param [String, Hash] val @return [Boolean]

# File lib/e-core/instance/cookies.rb, line 31
def []= key, val
  @response.set_cookie key, val
end
action() click to toggle source
# File lib/e-core/instance/base.rb, line 27
def action
  action_setup && action_setup[:action]
end
action_name() click to toggle source
# File lib/e-core/instance/base.rb, line 45
def action_name
  action_setup[:action_name]
end
action_params() click to toggle source

@example

def index id, status
  action_params
end
# GET /100/active
# => {:id => '100', :status => 'active'}
# File lib/e-core/instance/base.rb, line 140
def action_params
  return @__e__action_params if @__e__action_params

  action_params, given_params = {}, Array.new(action_params__array) # faster than dup
  action_setup[:action_arguments].each_with_index do |type_name, index|
    type, name = type_name
    if type == :rest
      action_params[name] = []
      until given_params.size < (action_setup[:action_arguments].size - index)
        action_params[name] << given_params.shift
      end
    else
      action_params[name] = given_params.shift
    end
  end
  @__e__action_params = EUtils.indifferent_params(action_params).freeze
end
action_params__array() click to toggle source
# File lib/e-core/instance/base.rb, line 127
def action_params__array
  @__e__action_params__array ||=
    (env[EConstants::ENV__ESPRESSO_PATH_INFO] || 
      env[EConstants::ENV__PATH_INFO]).to_s.split('/').reject(&:empty?).freeze
end
action_with_format() click to toggle source
# File lib/e-core/instance/base.rb, line 54
def action_with_format
  @__e__action_with_format ||= (format ? action.to_s + format : action).freeze
end
alias_actions() click to toggle source
# File lib/e-core/instance/base.rb, line 191
def alias_actions
  self.class.alias_actions[action] || []
end
attachment(path, opts = {}) click to toggle source

same as ‘send_file` except it instruct browser to display save dialog

# File lib/e-core/instance/send_file.rb, line 41
def attachment path, opts = {}
  halt send_file path, opts.merge(:attachment => true)
end
auth(opts = {})
Alias for: basic_auth
back() click to toggle source

Sugar for redirect (example: redirect back)

# File lib/e-core/instance/helpers.rb, line 43
def back
  request.referer
end
basic_auth(opts = {}) click to toggle source

@example protecting all actions via Basic authorization

auth { |user, pass| ['user', 'pass'] == [user, pass] }

@example protecting only :edit action

setup :edit do
  auth { |user, pass| ['user', 'pass'] == [user, pass] }
end

@example protecting only :edit and :delete actions

setup :edit, :delete do
  auth { |user, pass| ['user', 'pass'] == [user, pass] }
end

@params [Hash] opts @option opts [String] :realm

default - AccessRestricted

@param [Proc] proc

# File lib/e-core/instance/setup/auth.rb, line 21
def basic_auth opts = {}, &proc
  __e__authorize! Rack::Auth::Basic, opts[:realm] || 'AccessRestricted', &proc
end
Also aliased as: auth
cache_control(*values) click to toggle source

Specify response freshness policy for HTTP caches (Cache-Control header). Any number of non-value directives (:public, :private, :no_cache, :no_store, :must_revalidate, :proxy_revalidate) may be passed along with a Hash of value directives (:max_age, :min_stale, :s_max_age).

cache_control :public, :must_revalidate, :max_age => 60
=> Cache-Control: public, must-revalidate, max-age=60

See RFC 2616 / 14.9 for more on standard cache control directives: tools.ietf.org/html/rfc2616#section-14.9.1

# File lib/e-core/instance/setup/generic.rb, line 65
def cache_control(*values)
  if values.last.kind_of?(Hash)
    hash = values.pop
    hash.reject! { |k,v| v == false }
    hash.reject! { |k,v| values << k if v == true }
  else
    hash = {}
  end

  values.map! { |value| value.to_s.tr('_','-') }
  hash.each do |key, value|
    key = key.to_s.tr('_', '-')
    value = value.to_i if key == "max-age"
    values << [key, value].join('=')
  end

  response['Cache-Control'] = values.join(', ') if values.any?
end
call(env) click to toggle source
# File lib/e-core/instance/base.rb, line 62
def call env
  
  @__e__env     = env
  @__e__request = ERequest.new(env)
  @__e__format  = env[EConstants::ENV__ESPRESSO_FORMAT]

  e_response = catch :__e__catch__response__ do
    
    setup_action! unless action_setup

    min, max = action_setup[:required_arguments]
    given = action_params__array.size

    min && given < min &&
      styled_halt(EConstants::STATUS__NOT_FOUND, 'min params accepted: %s; params given: %s' % [min, given])

    max && given > max &&
      styled_halt(EConstants::STATUS__NOT_FOUND, 'max params accepted: %s; params given: %s' % [max, given])

    call!
  end
  e_response.body = [] if request.head?
  e_response.finish
end
call_setups!(position = :a) click to toggle source
# File lib/e-core/instance/base.rb, line 123
def call_setups! position = :a
  setups(position).each {|m| self.send m}
end
canonical() click to toggle source
# File lib/e-core/instance/base.rb, line 49
def canonical
  action_setup[:canonical]
end
Also aliased as: canonical?
canonical?()
Alias for: canonical
charset(charset) click to toggle source
# File lib/e-core/instance/setup/generic.rb, line 41
def charset charset
  content_type :charset => charset
end
chunk_stream(keep_open = true, &proc)
Alias for: chunked_stream
chunked_stream(keep_open = true, &proc) click to toggle source
# File lib/e-core/instance/stream.rb, line 7
def chunked_stream keep_open = true, &proc
  transfer_encoding 'chunked'
  streamer EStream::Chunked, keep_open, &proc
end
Also aliased as: chunk_stream
client_error?() click to toggle source

whether or not the status is set to 4xx

# File lib/e-core/instance/helpers.rb, line 63
def client_error?
  status.between? 400, 499
end
content_type(*args) click to toggle source

set/get or update Content-Type header.

if no args given, actual Content-Type returned.

To set charset alongside Content-Type, use :charset option.

@example all actions matching /api/ will return JSON Content-Type

class App < E

  setup /api/ do
    content_type '.json'
  end

  # ...
end

@param [Array] args

# File lib/e-core/instance/setup/generic.rb, line 20
def content_type *args
  return response[EConstants::HEADER__CONTENT_TYPE] if args.empty?
  type, opts = nil, {}
  args.each {|a| a.is_a?(Hash) ? opts.update(a) : type = a}
  if type
    type = mime_type(type, type)
  else
    if actual = response[EConstants::HEADER__CONTENT_TYPE]
      type, charset = actual.split(';')
      opts[:charset] ||= charset
    else
      type = EConstants::CONTENT_TYPE__DEFAULT
    end
  end
  if charset = opts[:charset]
    type = '%s; charset=%s' % [type, charset]
  end
  response[EConstants::HEADER__CONTENT_TYPE] = type
end
cookies() click to toggle source

shorthand for ‘response.set_cookie` and `response.delete_cookie`.

@example Setting a cookie cookies = ‘value’

@example Reading a cookie cookies #=> value

@example Setting a cookie with custom options cookies = {:value => ‘who is not who?’, :expires => Date.today + 1, :secure => true}

@example Deleting a cookie cookies.delete ‘cookie-name’

# File lib/e-core/instance/cookies.rb, line 18
def cookies
  @__e__cookies_proxy ||= Class.new do

    def initialize controller
      @controller, @request, @response =
        controller, controller.request, controller.response
    end

    # set cookie header
    #
    # @param [String, Symbol] key
    # @param [String, Hash] val
    # @return [Boolean]
    def []= key, val
      @response.set_cookie key, val
    end

    # get cookie by key
    def [] key
      @request.cookies[key]
    end

    # instruct browser to delete a cookie
    #
    # @param [String, Symbol] key
    # @param [Hash] opts
    # @return [Boolean]
    def delete key, opts ={}
      @response.delete_cookie key, opts
    end

    def method_missing *args
      @request.cookies.send *args
    end
  end.new self
end
deferred_redirect(*args)
Alias for: delayed_redirect
delayed_redirect(*args) click to toggle source

ensure the browser will be redirected after code execution finished

# File lib/e-core/instance/redirect.rb, line 25
def delayed_redirect *args
  status = args.first.is_a?(Numeric) ? args.shift : EConstants::STATUS__REDIRECT
  app = EUtils.is_app?(args.first) ? args.shift : nil
  action = args.first.is_a?(Symbol) ? args.shift : nil
  if app && action
    target = app.route action, *args
  elsif app
    target = app.route *args
  elsif action
    target = route action, *args
  else
    target = EUtils.build_path *args
  end
  response.body = []
  response.redirect target, status
end
Also aliased as: deferred_redirect
delete(key, opts ={}) click to toggle source

instruct browser to delete a cookie

@param [String, Symbol] key @param [Hash] opts @return [Boolean]

# File lib/e-core/instance/cookies.rb, line 45
def delete key, opts ={}
  @response.delete_cookie key, opts
end
digest_auth(opts = {}) click to toggle source

@example digest auth - hashed passwords:

# hash the password somewhere in irb:
# ::Digest::MD5.hexdigest 'admin:AccessRestricted:somePassword'
#                   username ^      realm ^       password ^

#=> 9d77d54decc22cdcfb670b7b79ee0ef0

digest_auth :passwords_hashed => true, :realm => 'AccessRestricted' do |user|
  {'admin' => '9d77d54decc22cdcfb670b7b79ee0ef0'}[user]
end

@example digest auth - plain password

digest_auth do |user|
  {'admin' => 'password'}[user]
end

@params [Hash] opts @option opts [String] :realm

default - AccessRestricted

@option opts [String] :opaque

default - same as realm

@option opts [Boolean] :passwords_hashed

default - false

@param [Proc] proc

# File lib/e-core/instance/setup/auth.rb, line 53
def digest_auth opts = {}, &proc
  opts[:realm]  ||= 'AccessRestricted'
  opts[:opaque] ||= opts[:realm]
  __e__authorize! Rack::Auth::Digest::MD5, *[opts], &proc
end
engine(engine, engine_opts = {}) click to toggle source

@example - use Haml for all actions

engine :Haml

@example - use Haml only for :news and :articles actions

class App < E
  # ...
  setup :news, :articles do
    engine :Haml
  end
end

@example engine with opts

engine :Haml, :some_engine_argument, some_option: 'some value'

@param [Symbol] engine

accepts any of Tilt supported engine

@param [String] *args

any args to be passed to engine at initialization
# File lib/e-more/view/setup.rb, line 21
def engine engine, engine_opts = {}
  EUtils.register_extra_engines!
  engine = EConstants::VIEW__ENGINE_BY_SYM[engine] ||
    raise(ArgumentError, '%s engine not supported. Supported engines: %s' %
      [engine, EConstants::VIEW__ENGINE_BY_SYM.keys.join(', ')])
  @__e__engine = [engine, engine_opts].freeze
end
engine?() click to toggle source
# File lib/e-more/view/base.rb, line 28
def engine?
  @__e__engine || EConstants::VIEW__DEFAULT_ENGINE
end
engine_ext(ext) click to toggle source

set the extension used by templates

# File lib/e-more/view/setup.rb, line 31
def engine_ext ext
  @__e__engine_ext = ext.freeze
end
engine_ext?() click to toggle source
# File lib/e-more/view/base.rb, line 32
def engine_ext?
  @__e__engine_ext || EConstants::VIEW__EXT_BY_ENGINE[engine?.first] || ''
end
engine_ext_with_format() click to toggle source
# File lib/e-more/view/base.rb, line 36
def engine_ext_with_format
  @__e__engine_ext_with_format ||= (format.to_s + engine_ext?).freeze
end
env() click to toggle source
# File lib/e-core/instance/base.rb, line 9
def env
  @__e__env
end
error(error_code = EConstants::STATUS__SERVER_ERROR, body = nil)
Alias for: styled_halt
error!(error_code = EConstants::STATUS__SERVER_ERROR, body = nil)
Alias for: styled_halt
error_handler_defined?(error_code) click to toggle source
# File lib/e-core/instance/halt.rb, line 85
def error_handler_defined? error_code
  self.class.error_handler(error_code) || self.class.error_handler(:*)
end
escape_element(*args) click to toggle source
# File lib/e-core/instance/helpers.rb, line 26
def escape_element *args
  ::CGI.escapeElement *args
end
escape_html(*args) click to toggle source
# File lib/e-core/instance/helpers.rb, line 18
def escape_html *args
  ::CGI.escapeHTML *args
end
etag(value, options = {}) click to toggle source

Set the response entity tag (HTTP ‘ETag’ header) and halt if conditional GET matches. The value argument is an identifier that uniquely identifies the current version of the resource. The kind argument indicates whether the etag should be used as a :strong (default) or :weak cache validator.

When the current request includes an ‘If-None-Match’ header with a matching etag, execution is immediately halted. If the request method is GET or HEAD, a ‘304 Not Modified’ response is sent.

# File lib/e-core/instance/setup/generic.rb, line 149
def etag(value, options = {})
  # Before touching this code, please double check RFC 2616 14.24 and 14.26.
  options      = {:kind => options} unless Hash === options
  kind         = options[:kind] || :strong
  new_resource = options.fetch(:new_resource) { request.post? }

  unless [:strong, :weak].include?(kind)
    raise ArgumentError, ":strong or :weak expected"
  end

  value = '"%s"' % value
  value = 'W/' + value if kind == :weak
  response['ETag'] = value

  if success? or status == 304
    if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
      halt(request.safe? ? 304 : 412)
    end

    if env['HTTP_IF_MATCH']
      halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
    end
  end
end
etag_matches?(list, new_resource = request.post?) click to toggle source

Helper method checking if a ETag value list includes the current ETag.

# File lib/e-core/instance/setup/generic.rb, line 202
def etag_matches?(list, new_resource = request.post?)
  return !new_resource if list == '*'
  list.to_s.split(/\s*,\s*/).include? response['ETag']
end
event_stream(keep_open = true, &proc)
Alias for: evented_stream
evented_stream(keep_open = true, &proc) click to toggle source
# File lib/e-core/instance/stream.rb, line 13
def evented_stream keep_open = true, &proc
  content_type EConstants::CONTENT_TYPE__EVENT_STREAM
  streamer EStream::Evented, keep_open, &proc
end
Also aliased as: event_stream
expires(amount, *values) click to toggle source

Set the Expires header and Cache-Control/max-age directive. Amount can be an integer number of seconds in the future or a Time object indicating when the response should be considered “stale”. The remaining “values” arguments are passed to the cache_control helper:

expires 500, :public, :must_revalidate
=> Cache-Control: public, must-revalidate, max-age=60
=> Expires: Mon, 08 Jun 2009 08:50:17 GMT
# File lib/e-core/instance/setup/generic.rb, line 94
def expires(amount, *values)
  values << {} unless values.last.kind_of?(Hash)

  if amount.is_a? Integer
    time    = Time.now + amount.to_i
    max_age = amount
  else
    time    = time_for amount
    max_age = time - Time.now
  end

  values.last.merge!(:max_age => max_age)
  cache_control(*values)

  response['Expires'] = time.httpdate
end
explicit_view_path(*args) click to toggle source
# File lib/e-more/view/base.rb, line 60
def explicit_view_path *args
  EView__ExplicitPath.new File.join(*args)
end
fail(error_code = EConstants::STATUS__SERVER_ERROR, body = nil)
Alias for: styled_halt
fail!(error_code = EConstants::STATUS__SERVER_ERROR, body = nil)
Alias for: styled_halt
fetch(*args, &proc) click to toggle source

same as ‘invoke` except it returns only body

# File lib/e-core/instance/invoke.rb, line 87
def fetch *args, &proc
  body = invoke(*args, &proc).last
  body = body.body if body.respond_to?(:body)
  body.respond_to?(:join) ? body.join : body
end
flash() click to toggle source

@example

flash[:alert] = 'Item Deleted'
p flash[:alert] #=> "Item Deleted"
p flash[:alert] #=> nil
# File lib/e-core/instance/session.rb, line 42
def flash
  @__e__flash_proxy ||= Class.new do

    def initialize session = {}
      @session = session
    end

    def []= key, val
      @session[key(key)] = val
    end

    def [] key
      return unless val = @session[key = key(key)]
      @session.delete key
      val
    end

    def key key
      '__e__session__flash__-' << key.to_s
    end
  end.new env['rack.session']
end
format() click to toggle source
# File lib/e-core/instance/base.rb, line 58
def format
  @__e__format
end
formats() click to toggle source
# File lib/e-core/instance/base.rb, line 179
def formats
  self.class.formats action
end
halt(*args) click to toggle source

stop executing any code and send response to browser.

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.

@example returning “Well Done” body with 200 status code

halt 'Well Done'

@example halting quietly, with empty body and 200 status code

halt

@example returning error with 500 code:

halt 500, 'Sorry, some fatal error occurred'

@example custom content type

halt File.read('/path/to/theme.css'), 'Content-Type' => mime_type('.css')

@example sending custom Rack response

halt [200, {'Content-Disposition' => "attachment; filename=some-file"}, some_IO_instance]

@param [Array] *args

# File lib/e-core/instance/halt.rb, line 28
def halt *args
  args.each do |a|
    case a
      when Fixnum
        response.status = a
      when Array
        status, headers, body = a
        response.status = status
        response.headers.update headers
        response.body = body
      when Hash
        response.headers.update a
      else
        response.body = [a.to_s]
    end
  end
  response.body ||= []
  throw :__e__catch__response__, response
end
informational?() click to toggle source

whether or not the status is set to 1xx

# File lib/e-core/instance/helpers.rb, line 48
def informational?
  status.between? 100, 199
end
initialize_controller(action = nil) click to toggle source
# File lib/e-core/instance/base.rb, line 5
def initialize_controller action = nil
  @__e__action_passed_at_initialize = action
end
invoke(*args) { |env| ... } click to toggle source

invoke some action via HTTP. to invoke an action on inner controller, pass controller as first argument and the action as second.

@note unlike ‘pass`, `invoke` will not pass any data!

@note it will use current REQUEST_METHOD to issue a request.

to use another request method use #[pass|invoke|fetch]_via_[verb]
ex: #pass_via_get, #fetch_via_post etc

@note to update passed env, use a block.

the block will receive the env as first argument
and therefore you can update it as needed.

@param [Class] *args

# File lib/e-core/instance/invoke.rb, line 45
def invoke *args

  if args.empty?
    body = '`invoke` expects some action(or a Controller and some action) to be provided'
    return [EConstants::STATUS__BAD_REQUEST, {}, [body]]
  end

  controller = EUtils.is_app?(args.first) ? args.shift : self.class

  if args.empty?
    body = 'Beside Controller, `invoke` expects some action to be provided'
    return [EConstants::STATUS__BAD_REQUEST, {}, [body]]
  end

  action = args.shift.to_sym
  unless route = controller[action]
    body = '%s does not respond to %s action' % [controller, action]
    return [EConstants::STATUS__NOT_FOUND, {}, [body]]
  end

  env = Hash[env()] # faster than #dup
  yield(env) if block_given?
  env[EConstants::ENV__SCRIPT_NAME]  = route
  env[EConstants::ENV__PATH_INFO]    = ''
  env[EConstants::ENV__QUERY_STRING] = ''
  env[EConstants::ENV__REQUEST_URI]  = ''
  env[EConstants::ENV__ESPRESSO_PATH_INFO] = nil

  if args.size > 0
    path, params = [''], {}
    args.each { |a| a.is_a?(Hash) ? params.update(a) : path << a }
    env[EConstants::ENV__PATH_INFO] = env[EConstants::ENV__REQUEST_URI] = path.join('/')
    
    if params.any?
      env.update(EConstants::ENV__QUERY_STRING => build_nested_query(params))
      env['rack.input'] = StringIO.new
    end
  end
  controller.new(action).call(env)
end
invoke_action() click to toggle source
# File lib/e-core/instance/base.rb, line 119
def invoke_action
  self.send(action, *action_params__array)
end
key(key) click to toggle source
# File lib/e-core/instance/session.rb, line 59
def key key
  '__e__session__flash__-' << key.to_s
end
keys() click to toggle source
# File lib/e-core/instance/session.rb, line 19
def keys
  @session.to_hash.keys
end
last_modified(time) click to toggle source

Set the last modified time of the resource (HTTP ‘Last-Modified’ header) and halt if conditional GET matches. The time argument is a Time, DateTime, or other object that responds to to_time.

When the current request includes an ‘If-Modified-Since’ header that is equal or later than the time specified, execution is immediately halted with a ‘304 Not Modified’ response.

# File lib/e-core/instance/setup/generic.rb, line 119
def last_modified(time)
  return unless time
  time = time_for time
  response['Last-Modified'] = time.httpdate
  return if env['HTTP_IF_NONE_MATCH']

  if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
    # compare based on seconds since epoch
    since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
    halt 304 if since >= time.to_i
  end

  if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
    # compare based on seconds since epoch
    since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
    halt 412 if since < time.to_i
  end
rescue ArgumentError
end
layout(layout = nil, &proc) click to toggle source

set the layout to be used by some or all actions.

@note

by default no layout will be rendered.
if you need layout, use `layout` to set it.

@example set :master layout for :index and :register actions

class Example < E
  setup :index, :register do
    layout :master
  end
end

@example instruct :plain and :json actions to not use layout

class Example < E
  setup :plain, :json do
    layout false
  end
end

@example use a block for layout

class Example < E
  layout do
    <<-HTML
        header
        <%= yield %>
        footer
    HTML
  end
end

@param layout @param [Proc] &proc

# File lib/e-more/view/setup.rb, line 72
def layout layout = nil, &proc
  @__e__layout = layout == false ? nil : [layout, proc].freeze
end
layout?() click to toggle source
# File lib/e-more/view/base.rb, line 40
def layout?
  @__e__layout
end
layout_path(path)
Alias for: layouts_path
layouts_path(path) click to toggle source

set custom path for layouts. default value: view path

@note should be relative to view path

# File lib/e-more/view/setup.rb, line 116
def layouts_path path
  @__e__layouts_path = path.freeze
end
Also aliased as: layout_path
layouts_path?() click to toggle source
# File lib/e-more/view/base.rb, line 24
def layouts_path?
  @__e__layouts_path || ''
end
method_missing(*args) click to toggle source
# File lib/e-core/instance/cookies.rb, line 49
def method_missing *args
  @request.cookies.send *args
end
mime_type(type, fallback = nil) click to toggle source

shortcut for Rack::Mime::MIME_TYPES.fetch

# File lib/e-core/instance/helpers.rb, line 14
def mime_type type, fallback = nil
  Rack::Mime::MIME_TYPES.fetch type, fallback
end
not_found?() click to toggle source

whether or not the status is set to 404

# File lib/e-core/instance/helpers.rb, line 73
def not_found?
  status == 404
end
params() click to toggle source
# File lib/e-core/instance/base.rb, line 23
def params
  @__e__params ||= EUtils.indifferent_params(request.params)
end
pass(*args, &proc) click to toggle source

simply pass control and data to another action or controller.

by default, it will pass control to an action on current controller. however, if first argument is a controller, control will be passed to it.

@example pass control to control_panel if user authorized

def index
  pass :control_panel if user?
end

@example passing with modified arguments and custom HTTP params

def index id, column
  pass :update, column, :value => id
end

@example passing control to inner controller

def index id
  pass Articles, :render_item, id
end

@param [Array] *args

# File lib/e-core/instance/invoke.rb, line 25
def pass *args, &proc
  args << params() unless args.any? {|a| a.is_a?(Hash)}
  halt invoke(*args, &proc)
end
path_to_layouts(*args) click to toggle source

returns full path to layouts. if any args given they are ‘File.join`-ed and appended to returned path.

# File lib/e-more/view/base.rb, line 56
def path_to_layouts *args
  explicit_view_path view_path?, layouts_path?, *args
end
path_to_templates(*args) click to toggle source

returns full path to templates. if any args given they are ‘File.join`-ed and appended to returned path.

@note this method will not make use of ‘view_prefix`,

thus you should provide full path to template, relative to `view_path` of course
# File lib/e-more/view/base.rb, line 50
def path_to_templates *args
  explicit_view_path view_path?, *args
end
permanent_redirect(*args) click to toggle source

same as redirect except it redirects with 301 status code

# File lib/e-core/instance/redirect.rb, line 19
def permanent_redirect *args
  delayed_redirect EConstants::STATUS__PERMANENT_REDIRECT, *args
  halt
end
quit(error_code = EConstants::STATUS__SERVER_ERROR, body = nil)
Alias for: styled_halt
quit!(error_code = EConstants::STATUS__SERVER_ERROR, body = nil)
Alias for: styled_halt
redirect(*args) click to toggle source

stop any code execution and redirect right away with 302 status code. path is built by passing given args to route

# File lib/e-core/instance/redirect.rb, line 13
def redirect *args
  delayed_redirect EConstants::STATUS__REDIRECT, *args
  halt
end
redirect?() click to toggle source

whether or not the status is set to 3xx

# File lib/e-core/instance/helpers.rb, line 58
def redirect?
  status.between? 300, 399
end
reload(params = nil) click to toggle source

simply reload the page, using current GET params. to use custom GET params, pass a hash as first argument.

@param [Hash, nil] params

# File lib/e-core/instance/redirect.rb, line 7
def reload params = nil
  redirect request.path, params || request.GET
end
render(*args, &proc) click to toggle source

render a template with layout(if any defined). if no template given, it will use current action name as template. extension will be automatically added, based on format and engine extension.

# File lib/e-more/view/base.rb, line 67
def render *args, &proc
  template, scope, locals = __e__engine_arguments(args)
  engine_class, engine_opts = engine?
  engine_args = proc ? [engine_opts] : [__e__template(template), engine_opts]
  output = __e__engine_instance(engine_class, *engine_args, &proc).render(scope, locals)

  layout, layout_proc = layout?
  return output unless layout || layout_proc

  engine_args = layout_proc ? [engine_opts] : [__e__layout_template(layout), engine_opts]
  __e__engine_instance(engine_class, *engine_args, &layout_proc).render(scope, locals) { output }
end
render_f(template, *args)
Alias for: render_file
render_file(template, *args) click to toggle source

render a template by name. it requires full template name, eg. with extension.

# File lib/e-more/view/base.rb, line 106
def render_file template, *args
  template = path_to_templates(template) unless template.instance_of?(EView__ExplicitPath)
  render_partial template, *args
end
Also aliased as: render_f
render_l(*args, &proc)
Alias for: render_layout
render_layout(*args, &proc) click to toggle source

render a layout. if no layout given, it will use the layout defined for current action(if any). extension will be automatically added, based on format and engine extension.

# File lib/e-more/view/base.rb, line 94
def render_layout *args, &proc
  layout, scope, locals = __e__engine_arguments(args, nil)
  layout, layout_proc = layout ? layout : layout?
  layout || layout_proc || raise('No explicit layout given nor implicit layout found' % action)
  engine_class, engine_opts = engine?
  engine_args = layout_proc ? [engine_opts] : [__e__layout_template(layout), engine_opts]
  __e__engine_instance(engine_class, *engine_args, &layout_proc).render(scope, locals, &(proc || Proc.new {''}))
end
Also aliased as: render_l
render_layout_file(template, *args, &proc) click to toggle source

render a layout. it requires full layout name, eg. with extension.

# File lib/e-more/view/base.rb, line 114
def render_layout_file template, *args, &proc
  template = path_to_layouts(template) unless template.instance_of?(EView__ExplicitPath)
  render_layout template, *args, &proc
end
Also aliased as: render_lf
render_lf(template, *args, &proc)
Alias for: render_layout_file
render_p(*args, &proc)
Alias for: render_partial
render_partial(*args, &proc) click to toggle source

render a template without layout. if no template given, it will use current action name as template. extension will be automatically added, based on format and engine extension.

# File lib/e-more/view/base.rb, line 83
def render_partial *args, &proc
  template, scope, locals = __e__engine_arguments(args)
  engine_class, engine_opts = engine?
  engine_args = proc ? [engine_opts] : [__e__template(template), engine_opts]
  __e__engine_instance(engine_class, *engine_args, &proc).render(scope, locals)
end
Also aliased as: render_p
request() click to toggle source
# File lib/e-core/instance/base.rb, line 13
def request
  @__e__request
end
Also aliased as: rq
request_token_auth(realm = 'Application') click to toggle source
# File lib/e-core/instance/setup/auth.rb, line 136
def request_token_auth realm = 'Application'
  ETokenAuth.new(self).request_token_auth(realm)
end
Also aliased as: request_token_auth!
request_token_auth!(realm = 'Application')
Alias for: request_token_auth
response() click to toggle source
# File lib/e-core/instance/base.rb, line 18
def response
  @__e__response ||= EResponse.new
end
Also aliased as: rs
route(*args) click to toggle source
# File lib/e-core/instance/base.rb, line 187
def route *args
  self.class.route *args
end
rq()
Alias for: request
rs()
Alias for: response
send_file(path, opts = {}) click to toggle source

Serving static files. Note that this blocks app while file readed/transmitted(on WEBrick and Thin, as minimum). To avoid app locking, setup your Nginx/Lighttpd server to set proper X-Sendfile header and use Rack::Sendfile middleware in your app.

@param [String] path full path to file @param [Hash] opts @option opts [String] filename the name of file displayed in browser’s save dialog @option opts [String] content_type custom content_type @option opts [String] last_modified @option opts [String] cache_control @option opts [Boolean] attachment if set to true, browser will prompt user to save file

# File lib/e-core/instance/send_file.rb, line 15
def send_file path, opts = {}

  file = ::Rack::File.new nil
  file.path = path
  (cache_control = opts[:cache_control]) && (file.cache_control = cache_control)
  response = file.serving env

  response[1][EConstants::HEADER__CONTENT_DISPOSITION] = opts[:attachment] ?
      'attachment; filename="%s"' % (opts[:filename] || ::File.basename(path)) :
      'inline'

  (content_type = opts[:content_type]) &&
    (response[1][EConstants::HEADER__CONTENT_TYPE] = content_type)

  (last_modified = opts[:last_modified]) &&
    (response[1][EConstants::HEADER__LAST_MODIFIED] = last_modified)

  halt response
end
send_files(dir) click to toggle source

serve static files at dir path

# File lib/e-core/instance/send_file.rb, line 36
def send_files dir
  halt ::Rack::Directory.new(dir).call(env)
end
server_error?() click to toggle source

whether or not the status is set to 5xx

# File lib/e-core/instance/helpers.rb, line 68
def server_error?
  status.between? 500, 599
end
session() click to toggle source

a simple wrapper around Rack::Session

# File lib/e-core/instance/session.rb, line 4
def session
  @__e__session_proxy ||= Class.new do

    def initialize session = {}
      @session = session
    end

    def [] key
      @session[key]
    end

    def []= key, val
      @session[key] = val
    end

    def keys
      @session.to_hash.keys
    end

    def values
      @session.to_hash.values
    end

    def delete key
      @session.delete key
    end

    def method_missing *args
      @session.send *args
    end

  end.new env['rack.session']
end
setups(position) click to toggle source
# File lib/e-core/instance/base.rb, line 175
def setups position
  self.class.setups position, action, format
end
status(value=nil) click to toggle source

Set or retrieve the response status code.

# File lib/e-core/instance/helpers.rb, line 37
def status(value=nil)
  response.status = value if value
  response.status
end
stream(keep_open = false, &proc) click to toggle source
# File lib/e-core/instance/stream.rb, line 3
def stream keep_open = false, &proc
  streamer EStream::Generic, keep_open, &proc
end
styled_error(error_code = EConstants::STATUS__SERVER_ERROR, body = nil)
Alias for: styled_halt
styled_error!(error_code = EConstants::STATUS__SERVER_ERROR, body = nil)
Alias for: styled_halt
styled_halt(error_code = EConstants::STATUS__SERVER_ERROR, body = nil) click to toggle source

same as ‘halt` except it carrying earlier defined error handlers. if no handler found it behaves exactly as `halt(error_code[, body])`.

@example

class App < E

  # defining the proc to be executed on 404 errors
  error 404 do |message|
    render_layout('layouts/404') { message }
  end

  def index id, status
    item = Model.fisrt(:id => id, :status => status)
    unless item
      # interrupt execution and send a styled 404 error to browser.
      styled_halt 404, 'Can not find item by given ID and Status'
    end
    # code to be executed only if item found
  end
end
# File lib/e-core/instance/halt.rb, line 69
def styled_halt error_code = EConstants::STATUS__SERVER_ERROR, body = nil
  if handler = error_handler_defined?(error_code)
    meth, arity = handler
    body = arity > 0 ? self.send(meth, body) : [self.send(meth), body].join
  end
  halt error_code.to_i, body
end
success?() click to toggle source

whether or not the status is set to 2xx

# File lib/e-core/instance/helpers.rb, line 53
def success?
  status.between? 200, 299
end
time_for(value) click to toggle source

Generates a Time object from the given value. Used by expires and last_modified.

# File lib/e-core/instance/setup/generic.rb, line 177
def time_for(value)
  if value.respond_to? :to_time
    value.to_time
  elsif value.is_a? Time
    value
  elsif value.respond_to? :new_offset
    # DateTime#to_time does the same on 1.9
    d = value.new_offset 0
    t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
    t.getlocal
  elsif value.respond_to? :mday
    # Date#to_time does the same on 1.9
    Time.local(value.year, value.mon, value.mday)
  elsif value.is_a? Numeric
    Time.at value
  else
    Time.parse value.to_s
  end
rescue ArgumentError => boom
  raise boom
rescue Exception
  raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
end
token_auth(realm = 'Application', &proc) click to toggle source

Makes it dead easy to do HTTP Token authentication.

@example simple Token example

class App < E
  TOKEN = "secret".freeze

  setup :edit do
    token_auth { |token| token == TOKEN }
  end

  def index
    "Everyone can see me!"
  end

  def edit
    "I'm only accessible if you know the password"
  end

end

@example more advanced Token example

        where only Atom feeds and the XML API is protected by HTTP token authentication,
        the regular HTML interface is protected by a session approach

class App < E
  before :set_account do
    authenticate
  end

  def set_account
    @account = Account.find_by ...
  end

  private
  def authenticate
    if accept? /xml|atom/
      if user = valid_token_auth? { |t, o| @account.users.authenticate(t, o) }
        @current_user = user
      else
        request_token_auth!
      end
    else
      # session based authentication
    end
  end
end

In your integration tests, you can do something like this:

def test_access_granted_from_xml
  get(
    "/notes/1.xml", nil,
    'HTTP_AUTHORIZATION' => EUtils.encode_token_auth_credentials('some-token')
  )

  assert_equal 200, status
end

On shared hosts, Apache sometimes doesn’t pass authentication headers to FCGI instances. If your environment matches this description and you cannot authenticate, try this rule in your Apache setup:

RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
# File lib/e-core/instance/setup/auth.rb, line 126
def token_auth realm = 'Application', &proc
  validate_token_auth(&proc) || request_token_auth(realm)
end
transfer_encoding(encoding) click to toggle source
# File lib/e-core/instance/setup/generic.rb, line 46
def transfer_encoding encoding
  response[EConstants::HEADER__TRANSFER_ENCODING] = encoding
end
unescape_element(*args) click to toggle source
# File lib/e-core/instance/helpers.rb, line 30
def unescape_element *args
  ::CGI.unescapeElement *args
end
unescape_html(*args) click to toggle source
# File lib/e-core/instance/helpers.rb, line 22
def unescape_html *args
  ::CGI.unescapeHTML *args
end
user() click to toggle source
# File lib/e-core/instance/base.rb, line 195
def user
  env[EConstants::ENV__REMOTE_USER]
end
Also aliased as: user?
user?()
Alias for: user
valid_token_auth?(&proc)
Alias for: validate_token_auth
validate_token_auth(&proc) click to toggle source
# File lib/e-core/instance/setup/auth.rb, line 131
def validate_token_auth &proc
  ETokenAuth.new(self).validate_token_auth(&proc)
end
Also aliased as: valid_token_auth?
values() click to toggle source
# File lib/e-core/instance/session.rb, line 23
def values
  @session.to_hash.values
end
view_fullpath(path) click to toggle source
# File lib/e-more/view/setup.rb, line 84
def view_fullpath path
  @__e__view_fullpath = path.freeze
end
view_fullpath?() click to toggle source
# File lib/e-more/view/base.rb, line 16
def view_fullpath?
  @__e__view_fullpath
end
view_path(path) click to toggle source

set custom path for templates. default value: app_root/view/

# File lib/e-more/view/setup.rb, line 79
def view_path path
  @__e__view_path = path.freeze
end
view_path?() click to toggle source
# File lib/e-more/view/base.rb, line 9
def view_path?
  @__e__computed_view_path ||= begin
    (fullpath = view_fullpath?) ? fullpath :
      File.join(app.root, @__e__view_path || EConstants::VIEW__DEFAULT_PATH).freeze
  end
end
view_prefix(path) click to toggle source

allow setting view prefix

@note defaults to controller’s base_url

@example :index action will render ‘view/admin/reports/index.EXT’ view,

       regardless of base_url

class Reports < E
  map '/reports'
  view_prefix 'admin/reports'
  # ...
  def index
    render
  end

end

@param string

# File lib/e-more/view/setup.rb, line 107
def view_prefix path
  @__e__view_prefix = path.freeze
end
view_prefix?() click to toggle source
# File lib/e-more/view/base.rb, line 20
def view_prefix?
  @__e__view_prefix || default_route
end
websocket?() click to toggle source
# File lib/e-core/instance/stream.rb, line 19
def websocket?
  # on websocket requests, Reel web-server storing the socket into ENV['rack.websocket']
  # TODO: implement rack.hijack API
  env[EConstants::RACK__WEBSOCKET]
end
xhr?() click to toggle source
# File lib/e-core/instance/helpers.rb, line 9
def xhr?
  (@__e__requested_with_map ||= {env["HTTP_X_REQUESTED_WITH"] => true})["XMLHttpRequest"]
end

Private Instance Methods

__e__authorize!(auth_class, *auth_args, &auth_proc) click to toggle source
# File lib/e-core/instance/setup/auth.rb, line 142
def __e__authorize! auth_class, *auth_args, &auth_proc
  if auth_required = auth_class.new(proc {}, *auth_args, &auth_proc).call(env)
    halt auth_required
  end
end
__e__engine_arguments(args, template = action) click to toggle source
# File lib/e-more/view/base.rb, line 167
def __e__engine_arguments args, template = action
  scope, locals = self, {}
  args.compact.each do |arg|
    case arg
    when String
      template = arg
    when Symbol
      template = arg.to_s
    when Hash
      locals.update arg
    else
      scope = arg
    end
  end
  [template, scope, locals]
end
__e__engine_instance(engine, *args, &proc) click to toggle source
# File lib/e-more/view/base.rb, line 184
def __e__engine_instance engine, *args, &proc
  if compiler_pool && (tpl = args.first).is_a?(String)
    ((compiler_pool[tpl]||={})[File.mtime(tpl).to_i]||={})[engine.__id__] ||=
      engine.new(*args, &proc)
  else
    engine.new(*args, &proc)
  end
end
__e__layout_template(layout, ext = engine_ext_with_format) click to toggle source
# File lib/e-more/view/base.rb, line 198
def __e__layout_template layout, ext = engine_ext_with_format
  return layout if layout.instance_of?(EView__ExplicitPath)
  File.join(view_path?, layouts_path?, layout.to_s) << ext
end
__e__template(template, ext = engine_ext_with_format) click to toggle source
# File lib/e-more/view/base.rb, line 193
def __e__template template, ext = engine_ext_with_format
  return template if template.instance_of?(EView__ExplicitPath)
  File.join(view_path?, view_prefix?, template.to_s) << ext
end
call!() click to toggle source
# File lib/e-core/instance/base.rb, line 87
def call!
  call_setups! :a

  # automatically set Content-Type by given format, if any.
  # @note this will override Content-Type set via setups.
  #       to override Content-Type set by format,
  #       use #content_type inside action
  format && content_type(format)

  body, response.body = nil
  (wrappers = setups(:around)).any? ?
    wrappers.each {|w| body = self.send(w)} :
    body = invoke_action
  response.body ||= [body.to_s]

  call_setups! :z

  response[EConstants::HEADER__CONTENT_TYPE] ||= EConstants::CONTENT_TYPE__DEFAULT

  response
rescue => e
  # if a error handler defined, use it
  if handler = error_handler_defined?(EConstants::STATUS__SERVER_ERROR)
    meth, arity = handler
    halt EConstants::STATUS__SERVER_ERROR, arity > 0 ? self.send(meth, e) : self.send(meth)
  else
    # otherwise raise rescued exception
    raise e
  end
end
setup_action!(action = nil) click to toggle source
# File lib/e-core/instance/base.rb, line 31
def setup_action! action = nil
  if action ||= @__e__action_passed_at_initialize || env[EConstants::ENV__ESPRESSO_ACTION]
    if setup = self.class.action_setup[action]
      self.action_setup = setup[env[EConstants::ENV__REQUEST_METHOD]] || setup[:*]
      self.action_setup ||
        styled_halt(EConstants::STATUS__NOT_IMPLEMENTED, "Resource found
          but it can be accessed only through %s" % setup.keys.join(", "))
    end
  end
  self.action_setup ||
    styled_halt(EConstants::STATUS__NOT_FOUND, '%s %s not found' % [rq.request_method, rq.path])
end
streamer(streamer, keep_open = false) { |out| ... } click to toggle source

Allows to start sending data to the client even though later parts of the response body have not yet been generated.

The close parameter specifies whether Stream#close should be called after the block has been executed. This is only relevant for evented servers like Thin or Rainbows.

# File lib/e-core/instance/stream.rb, line 32
def streamer streamer, keep_open = false, &proc
  if app.streaming_backend == :Celluloid
    response.body = Reel::Stream.new(&proc)
  else
    scheduler = env['async.callback'] ? EventMachine : EStream::Generic
    current   = (@__e__params||{}).dup
    response.body = streamer.new(scheduler, keep_open) {|out| with_params(current) { yield(out) }}
  end
end
with_params(temp_params) { || ... } click to toggle source
# File lib/e-core/instance/stream.rb, line 42
def with_params(temp_params)
  original, @__e__params = @__e__params, temp_params
  yield
ensure
  @__e__params = original if original
end