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
@private Used internally to access the Iodine::Connection client data (if available).
A union between the `request.params` and the route's inline parameters. This is different then `request.params`
A Rack::Request object for the current request.
A Rack::Response object used for the current request.
Public Class Methods
# File lib/plezi/controller/controller.rb, line 15 def self.included(base) base.extend ::Plezi::Controller::ClassMethods end
Public Instance Methods
@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
@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
@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
@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
@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
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
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.
# 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
Returns an array with all the keys of any available cookies (both existing and new cookies).
# File lib/plezi/controller/cookies.rb, line 47 def keys (@request ? (super + request.cookies.keys) : super) end
@private Overload this method to handle event.
# File lib/plezi/controller/controller.rb, line 209 def on_close end
@private Overload this method to handle event.
# File lib/plezi/controller/controller.rb, line 213 def on_drained end
@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
@private Overload this method to handle event.
# File lib/plezi/controller/controller.rb, line 205 def on_open end
@private Overload this method to handle event.
# File lib/plezi/controller/controller.rb, line 217 def on_shutdown end
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
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
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
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
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
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
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
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
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
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
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
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
Returns an array with all the values of any available cookies (both existing and new cookies).
# File lib/plezi/controller/cookies.rb, line 52 def values (@request ? (super + request.cookies.values) : super) end
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
@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