class E
Attributes
Public Class Methods
@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
# File lib/e-core/controller/mounter.rb, line 42 def accept_automount? true unless @reject_automount end
# File lib/e-core/controller/setups_reader.rb, line 6 def action_setup; @__e__action_setup end
(see before)
# File lib/e-core/controller/setups_writer.rb, line 242 def after *matchers, &proc add_setup :z, *matchers, &proc end
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
# File lib/e-core/controller/setups_reader.rb, line 60 def alias_actions @__e__alias_actions || {} end
(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
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
# File lib/e-core/controller/setups_reader.rb, line 3 def app; @__e__app end
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
# File lib/e-core/controller/setups_reader.rb, line 21 def base_url @__e__base_url || default_route end
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
# File lib/e-core/controller/mounter.rb, line 7 def call env mount.call(env) end
# File lib/e-core/controller/setups_reader.rb, line 26 def canonicals @__e__canonicals || [] end
# File lib/e-core/controller/setups_reader.rb, line 30 def default_route @__e__default_route ||= EUtils.class_to_route(self.name).freeze end
# 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
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
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
# 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
# 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
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
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
# File lib/e-core/controller/setups_reader.rb, line 64 def formats action (@__e__expanded_formats || {})[action] || [] end
# 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
# File lib/e-core/controller/setups_reader.rb, line 5 def hosts; @__e__hosts || {} end
‘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
# 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
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
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
# File lib/e-core/controller/setups_reader.rb, line 8 def mapped?; @__e__base_url end
# File lib/e-core/controller/setups_reader.rb, line 87 def middleware @__e__middleware || [] end
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
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
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
# File lib/e-core/controller/setups_reader.rb, line 9 def mounted?; @__e__mounted || @__e__app end
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
# File lib/e-core/instance/cookies.rb, line 21 def initialize controller @controller, @request, @response = controller, controller.request, controller.response end
convenient when doing some setup based on format @example
on '.xml' do # ... end
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
# 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
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
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 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
# 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
# File lib/e-core/controller/setups_reader.rb, line 7 def rewrite_rules; @__e__rewrite_rules || [] end
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
# File lib/e-core/controller/setups_reader.rb, line 4 def routes; @__e__routes end
# File lib/e-core/controller/mounter.rb, line 3 def run *args mount.run *args end
# 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
# 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
# File lib/e-core/controller/setups_reader.rb, line 91 def subcontrollers (@__e__subcontrollers || []).uniq end
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
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
# 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
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
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
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
# 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
# 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
# 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
# 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
# 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
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
# 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
# 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
# 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
# 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
# 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
# File lib/e-core/instance/base.rb, line 183 def [] action self.class[action] end
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
# File lib/e-core/instance/base.rb, line 27 def action action_setup && action_setup[:action] end
# File lib/e-core/instance/base.rb, line 45 def action_name action_setup[:action_name] end
@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
# 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
# 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
# File lib/e-core/instance/base.rb, line 191 def alias_actions self.class.alias_actions[action] || [] end
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
Sugar for redirect (example: redirect back)
# File lib/e-core/instance/helpers.rb, line 43 def back request.referer end
@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
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
# 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
# File lib/e-core/instance/base.rb, line 123 def call_setups! position = :a setups(position).each {|m| self.send m} end
# File lib/e-core/instance/base.rb, line 49 def canonical action_setup[:canonical] end
# File lib/e-core/instance/setup/generic.rb, line 41 def charset charset content_type :charset => charset end
# 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
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
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
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
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
@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
@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
# File lib/e-more/view/base.rb, line 28 def engine? @__e__engine || EConstants::VIEW__DEFAULT_ENGINE end
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
# File lib/e-more/view/base.rb, line 32 def engine_ext? @__e__engine_ext || EConstants::VIEW__EXT_BY_ENGINE[engine?.first] || '' end
# 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
# File lib/e-core/instance/base.rb, line 9 def env @__e__env end
# 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
# File lib/e-core/instance/helpers.rb, line 26 def escape_element *args ::CGI.escapeElement *args end
# File lib/e-core/instance/helpers.rb, line 18 def escape_html *args ::CGI.escapeHTML *args end
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
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
# 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
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
# File lib/e-more/view/base.rb, line 60 def explicit_view_path *args EView__ExplicitPath.new File.join(*args) end
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
@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
# File lib/e-core/instance/base.rb, line 58 def format @__e__format end
# File lib/e-core/instance/base.rb, line 179 def formats self.class.formats action end
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
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
# File lib/e-core/instance/base.rb, line 5 def initialize_controller action = nil @__e__action_passed_at_initialize = action end
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
# File lib/e-core/instance/base.rb, line 119 def invoke_action self.send(action, *action_params__array) end
# File lib/e-core/instance/session.rb, line 59 def key key '__e__session__flash__-' << key.to_s end
# File lib/e-core/instance/session.rb, line 19 def keys @session.to_hash.keys end
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
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
# File lib/e-more/view/base.rb, line 40 def layout? @__e__layout end
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
# File lib/e-more/view/base.rb, line 24 def layouts_path? @__e__layouts_path || '' end
# File lib/e-core/instance/cookies.rb, line 49 def method_missing *args @request.cookies.send *args end
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
whether or not the status is set to 404
# File lib/e-core/instance/helpers.rb, line 73 def not_found? status == 404 end
# File lib/e-core/instance/base.rb, line 23 def params @__e__params ||= EUtils.indifferent_params(request.params) end
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
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
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
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
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
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
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 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 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
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
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
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
# File lib/e-core/instance/base.rb, line 13 def request @__e__request end
# File lib/e-core/instance/setup/auth.rb, line 136 def request_token_auth realm = 'Application' ETokenAuth.new(self).request_token_auth(realm) end
# File lib/e-core/instance/base.rb, line 18 def response @__e__response ||= EResponse.new end
# File lib/e-core/instance/base.rb, line 187 def route *args self.class.route *args end
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
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
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
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
# File lib/e-core/instance/base.rb, line 175 def setups position self.class.setups position, action, format end
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
# File lib/e-core/instance/stream.rb, line 3 def stream keep_open = false, &proc streamer EStream::Generic, keep_open, &proc end
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
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
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
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
# File lib/e-core/instance/setup/generic.rb, line 46 def transfer_encoding encoding response[EConstants::HEADER__TRANSFER_ENCODING] = encoding end
# File lib/e-core/instance/helpers.rb, line 30 def unescape_element *args ::CGI.unescapeElement *args end
# File lib/e-core/instance/helpers.rb, line 22 def unescape_html *args ::CGI.unescapeHTML *args end
# File lib/e-core/instance/base.rb, line 195 def user env[EConstants::ENV__REMOTE_USER] end
# File lib/e-core/instance/setup/auth.rb, line 131 def validate_token_auth &proc ETokenAuth.new(self).validate_token_auth(&proc) end
# File lib/e-core/instance/session.rb, line 23 def values @session.to_hash.values end
# File lib/e-more/view/setup.rb, line 84 def view_fullpath path @__e__view_fullpath = path.freeze end
# File lib/e-more/view/base.rb, line 16 def view_fullpath? @__e__view_fullpath end
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
# 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
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
# File lib/e-more/view/base.rb, line 20 def view_prefix? @__e__view_prefix || default_route end
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
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
# 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