module Plezi::Controller

This module contains the functionality provided to any Controller class.

This module will be included within every Class that is asigned to a route, providing the functionality without forcing an inheritance model.

Any Controller can suppoert WebSocket connections by either implementing an `on_message(data)` callback or setting the `@auto_dispatch` class instance variable to `true`.

Attributes

_pl__client[R]

@private Used internally to access the Iodine::Connection client data (if available).

cookies[R]

A cookie jar for both accessing and setting cookies. Unifies `request.set_cookie`, `request.delete_cookie` and `request.cookies` with a single Hash like inteface.

Read a cookie:

cookies["name"]

Set a cookie:

cookies["name"] = "value"
cookies["name"] = {value: "value", secure: true}

Delete a cookie:

cookies["name"] = nil
params[R]

A union between the `request.params` and the route's inline parameters. This is different then `request.params`

request[R]

A Rack::Request object for the current request.

response[R]

A Rack::Response object used for the current request.

Public Class Methods

included(base) click to toggle source
# File lib/plezi/controller/controller.rb, line 15
def self.included(base)
   base.extend ::Plezi::Controller::ClassMethods
end

Public Instance Methods

_pl_ad_httpreview(data) click to toggle source

@private This function is used internally by Plezi, do not call.

# File lib/plezi/controller/controller.rb, line 258
def _pl_ad_httpreview(data)
   return data.to_json if self.class._pl_is_ad? && data.is_a?(Hash)
   data
end
_pl_ad_map() click to toggle source

@private This function is used internally by Plezi, do not call.

# File lib/plezi/controller/controller.rb, line 199
def _pl_ad_map
   @_pl_ad_map ||= self.class._pl_ad_map.dup
end
_pl_ad_review(data) click to toggle source

@private This function is used internally by Plezi, do not call.

# File lib/plezi/controller/controller.rb, line 244
def _pl_ad_review(data)
  return data unless self.class._pl_is_ad?
  case data
  when Hash
     _pl__client.write data.to_json
  when String
     _pl__client.write data
     # when Array
     #   write data.to_json
  end
end
_pl_respond(request, response, params) click to toggle source

@private This function is used internally by Plezi, do not call.

# File lib/plezi/controller/controller.rb, line 48
def _pl_respond(request, response, params)
   @request = request
   @response = response
   @params = params
   @cookies = Cookies.new(request, response)
   mthd = requested_method
   # puts "m == #{m.nil? ? 'nil' : m.to_s}"
   return _pl_ad_httpreview(__send__(mthd)) if mthd
   false
end
_pl_ws_map() click to toggle source

@private This function is used internally by Plezi, do not call.

# File lib/plezi/controller/controller.rb, line 193
def _pl_ws_map
   @_pl_ws_map ||= self.class._pl_ws_map.dup
end
close() click to toggle source

Closes an SSE / WebSocket connection (raises an error unless the connection was already established).

# File lib/plezi/controller/controller.rb, line 143
def close
   _pl__client.close
end
extend(mod) click to toggle source

Experimental: takes a module to be used for Websocket callbacks events.

This function can only be called after a websocket connection was established (i.e., within the `on_open` callback).

This allows a module “library” to be used similar to the way “rooms” are used in node.js, so that a number of different Controllers can listen to shared events.

By dynamically extending a Controller instance using a module, Auto Dispatch events can be routed to the newly available methods.

Notice: It is impossible to `unextend` an extended module at this time.

Calls superclass method
# File lib/plezi/controller/controller.rb, line 181
def extend(mod)
   raise TypeError, '`mod` should be a module' unless mod.class == Module
   unless is_a?(mod)
     mod.extend ::Plezi::Controller::ClassMethods
     super(mod)
   end
   _pl_ws_map.update mod._pl_ws_map
   _pl_ad_map.update mod._pl_ad_map
end
keys() click to toggle source

Returns an array with all the keys of any available cookies (both existing and new cookies).

Calls superclass method
# File lib/plezi/controller/cookies.rb, line 47
def keys
   (@request ? (super + request.cookies.keys) : super)
end
on_close() click to toggle source

@private Overload this method to handle event.

# File lib/plezi/controller/controller.rb, line 209
def on_close
end
on_drained() click to toggle source

@private Overload this method to handle event.

# File lib/plezi/controller/controller.rb, line 213
def on_drained
end
on_message(data) click to toggle source

@private This function is used internally by Plezi, for Auto-Dispatch support do not call.

# File lib/plezi/controller/controller.rb, line 222
def on_message(data)
   json = nil
   begin
      json = JSON.parse(data, symbolize_names: true)
      # json.default_proc = Plezi.hash_proc_4symstr
   rescue
      puts 'AutoDispatch Warnnig: Received non-JSON message. Closing Connection.'
      close
      return
   end
   envt = _pl_ad_map[json[:event]] || _pl_ad_map[:unknown]
   if json[:event].nil? || envt.nil?
      puts _pl_ad_map
      puts "AutoDispatch Warnnig: JSON missing/invalid `event` name '#{json[:event]}' for class #{self.class.name}. Closing Connection."
      close
   end
   _pl__client.write("{\"event\":\"_ack_\",\"_EID_\":#{json[:_EID_].to_json}}") if json[:_EID_]
   _pl_ad_review __send__(envt, json)
end
on_open() click to toggle source

@private Overload this method to handle event.

# File lib/plezi/controller/controller.rb, line 205
def on_open
end
on_shutdown() click to toggle source

@private Overload this method to handle event.

# File lib/plezi/controller/controller.rb, line 217
def on_shutdown
end
open?() click to toggle source

Tests the known state for an SSE / WebSocket connection (the known state might not be the same as the actual state).

# File lib/plezi/controller/controller.rb, line 147
def open?
   _pl__client && _pl__client.open?
end
pending() click to toggle source

Returns the number of pending `write` operations that need to complete before the next `on_drained` callback is called.

# File lib/plezi/controller/controller.rb, line 151
def pending
   return 0 unless _pl__client
   _pl__client.pending
end
pre_connect() click to toggle source

Override this method to read / write cookies, perform authentication or perform validation before establishing a Websocket or SSE connecion.

Return `false` or `nil` to refuse the websocket connection.

# File lib/plezi/controller/controller.rb, line 133
def pre_connect
   true
end
publish(*args) click to toggle source

Publishes to a Pub/Sub stream / channel (routes to Iodine.publish).

# File lib/plezi/controller/controller.rb, line 167
def publish *args
   ::Iodine.publish *args
end
redirect_to(target, status = 302) click to toggle source

A shortcut for Rack's `response.redirect`.

# File lib/plezi/controller/controller.rb, line 120
def redirect_to(target, status = 302)
   response.redirect target, status
   true
end
render(template, &block) click to toggle source

Renders the requested template (should be a string, subfolders are fine).

Template name shouldn't include the template's extension or format - this allows for dynamic format template resolution, so that `json` and `html` requests can share the same code. i.e.

Plezi.templates = "views/"
render "users/index"

Using layouts (nested templates) is easy by using a block (a little different then other frameworks):

render("users/layout") { render "users/index" }
# File lib/plezi/controller/controller.rb, line 80
def render(template, &block)
   frmt = params['format'.freeze] || 'html'.freeze
   mime = nil
   ret = ::Plezi::Renderer.render "#{File.join(::Plezi.templates, template.to_s)}.#{frmt}", binding, &block
   response[Rack::CONTENT_TYPE] = mime if ret && !response.content_type && (mime = Rack::Mime.mime_type(".#{frmt}".freeze, nil))
   ret
end
requested_method() click to toggle source

Returns the method that was called by the HTTP request.

It's possible to override this method to change the default Controller behavior.

For Websocket connections this method is most likely to return :preform_upgrade

# File lib/plezi/controller/controller.rb, line 64
def requested_method
   params['_method'.freeze] = (params['_method'.freeze] || request.request_method.downcase).to_sym
   self.class._pl_params2method(params, request.env)
end
send_data(data, options = {}) click to toggle source

Sends a block of data, setting a file name, mime type and content disposition headers when possible. This should also be a good choice when sending large amounts of data.

By default, `send_data` sends the data as an attachment, unless `inline: true` was set.

If a mime type is provided, it will be used to set the Content-Type header. i.e. `mime: “text/plain”`

If a file name was provided, Rack will be used to find the correct mime type (unless provided). i.e. `filename: “sample.pdf”` will set the mime type to `application/pdf`

Available options: `:inline` (`true` / `false`), `:filename`, `:mime`.

# File lib/plezi/controller/controller.rb, line 97
def send_data(data, options = {})
   response.write data if data
   filename = options[:filename]
   # set headers
   content_disposition = options[:inline] ? 'inline'.dup : 'attachment'.dup
   content_disposition << "; filename=#{::File.basename(options[:filename])}" if filename
   cont_type = (options[:mime] ||= filename && Rack::Mime.mime_type(::File.extname(filename)))
   response['content-type'.freeze] = cont_type if cont_type
   response['content-disposition'.freeze] = content_disposition
   true
end
send_file(filename, options = {}) click to toggle source

Same as {#send_data}, but accepts a file name (to be opened and sent) rather then a String.

See {#send_data} for available options.

# File lib/plezi/controller/controller.rb, line 112
def send_file(filename, options = {})
   response['X-Sendfile'.freeze] = filename
   options[:filename] ||= File.basename(filename)
   filename = File.open(filename, 'rb'.freeze) # unless Iodine::Rack.public
   send_data filename, options
end
subscribe(*args, &block) click to toggle source

Subscribes to a Pub/Sub stream / channel or replaces an existing subscription to the same stream / channel (raises an error unless an SSE / WebSocket connection was established).

# File lib/plezi/controller/controller.rb, line 157
def subscribe *args, &block
   raise "WebSocket / SSE connection missing" unless _pl__client
   if(block)
      _pl__client.subscribe *args, &block
   else
      _pl__client.subscribe *args
   end
end
to_s() click to toggle source

Writes a line dlimited string of all the existing and the new cookies. i.e.:

name1=value1
name2=value2
# File lib/plezi/controller/cookies.rb, line 42
def to_s
   (@request ? (to_a + request.cookies.to_a) : to_a).map! { |pair| pair.join('=') } .join "\n"
end
url_for(func, params = {}) click to toggle source

Returns a relative URL for the controller, placing the requested parameters in the URL (inline, where possible and as query data when not possible).

# File lib/plezi/controller/controller.rb, line 126
def url_for(func, params = {})
   ::Plezi::Base::Router.url_for self.class, func, params
end
values() click to toggle source

Returns an array with all the values of any available cookies (both existing and new cookies).

Calls superclass method
# File lib/plezi/controller/cookies.rb, line 52
def values
   (@request ? (super + request.cookies.values) : super)
end
write(data) click to toggle source

Writes to an SSE / WebSocket connection (raises an error unless the connection was already established).

# File lib/plezi/controller/controller.rb, line 138
def write data
   _pl__client.write data
end

Private Instance Methods

preform_upgrade() click to toggle source

@private This function is used internally by Plezi, do not call.

# File lib/plezi/controller/controller.rb, line 267
def preform_upgrade
   return false unless pre_connect
   request.env[::Plezi::Base::Bridge::CONTROLLER_NAME] = self
   request.env['rack.upgrade'.freeze] = ::Plezi::Base::Bridge
   @params = @params.dup # disable memory saving (used a single object per thread)
   @_pl_ws_map = self.class._pl_ws_map.dup
   @_pl_ad_map = self.class._pl_ad_map.dup
   true
end