Module: Plezi::Controller

Included in:
Base::Err404Ctrl, Base::Err500Ctrl
Defined in:
lib/plezi/controller/controller.rb,
lib/plezi/controller/cookies.rb,
lib/plezi/controller/controller_class.rb

Overview

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`.

Defined Under Namespace

Modules: ClassMethods Classes: Cookies

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#_pl__clientObject (readonly)

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



44
45
46
# File 'lib/plezi/controller/controller.rb', line 44

def _pl__client
  @_pl__client
end

#cookiesObject (readonly)

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


40
41
42
# File 'lib/plezi/controller/controller.rb', line 40

def cookies
  @cookies
end

#paramsObject (readonly)

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



24
25
26
# File 'lib/plezi/controller/controller.rb', line 24

def params
  @params
end

#requestObject (readonly)

A Rack::Request object for the current request.



20
21
22
# File 'lib/plezi/controller/controller.rb', line 20

def request
  @request
end

#responseObject (readonly)

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



22
23
24
# File 'lib/plezi/controller/controller.rb', line 22

def response
  @response
end

Class Method Details

.included(base) ⇒ Object



15
16
17
# File 'lib/plezi/controller/controller.rb', line 15

def self.included(base)
   base.extend ::Plezi::Controller::ClassMethods
end

Instance Method Details

#_pl_ad_httpreview(data) ⇒ Object

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



258
259
260
261
# 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_mapObject

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



199
200
201
# 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) ⇒ Object

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



244
245
246
247
248
249
250
251
252
253
254
# 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) ⇒ Object

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



48
49
50
51
52
53
54
55
56
57
# 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_mapObject

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



193
194
195
# File 'lib/plezi/controller/controller.rb', line 193

def _pl_ws_map
   @_pl_ws_map ||= self.class._pl_ws_map.dup
end

#closeObject

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



143
144
145
# File 'lib/plezi/controller/controller.rb', line 143

def close
   _pl__client.close
end

#extend(mod) ⇒ Object

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.

Raises:

  • (TypeError)


181
182
183
184
185
186
187
188
189
# 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

#keysObject

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



47
48
49
# File 'lib/plezi/controller/cookies.rb', line 47

def keys
   (@request ? (super + request.cookies.keys) : super)
end

#on_closeObject

Overload this method to handle event.



209
210
# File 'lib/plezi/controller/controller.rb', line 209

def on_close
end

#on_drainedObject

Overload this method to handle event.



213
214
# File 'lib/plezi/controller/controller.rb', line 213

def on_drained
end

#on_message(data) ⇒ Object

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



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# 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_openObject

Overload this method to handle event.



205
206
# File 'lib/plezi/controller/controller.rb', line 205

def on_open
end

#on_shutdownObject

Overload this method to handle event.



217
218
# File 'lib/plezi/controller/controller.rb', line 217

def on_shutdown
end

#open?Boolean

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

Returns:

  • (Boolean)


147
148
149
# File 'lib/plezi/controller/controller.rb', line 147

def open?
   _pl__client && _pl__client.open?
end

#pendingObject

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



151
152
153
154
# File 'lib/plezi/controller/controller.rb', line 151

def pending
   return 0 unless _pl__client
   _pl__client.pending
end

#pre_connectObject

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.



133
134
135
# File 'lib/plezi/controller/controller.rb', line 133

def pre_connect
   true
end

#publish(*args) ⇒ Object

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



167
168
169
# File 'lib/plezi/controller/controller.rb', line 167

def publish *args
   ::Iodine.publish *args
end

#redirect_to(target, status = 302) ⇒ Object

A shortcut for Rack’s ‘response.redirect`.



120
121
122
123
# File 'lib/plezi/controller/controller.rb', line 120

def redirect_to(target, status = 302)
   response.redirect target, status
   true
end

#render(template, &block) ⇒ Object

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" }


80
81
82
83
84
85
86
# 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_methodObject

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



64
65
66
67
# 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 = {}) ⇒ Object

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`.



97
98
99
100
101
102
103
104
105
106
107
# 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 = {}) ⇒ Object

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

See #send_data for available options.



112
113
114
115
116
117
# 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) ⇒ Object

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).



157
158
159
160
161
162
163
164
# 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_sObject

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

name1=value1
name2=value2


42
43
44
# 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 = {}) ⇒ Object

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



126
127
128
# File 'lib/plezi/controller/controller.rb', line 126

def url_for(func, params = {})
   ::Plezi::Base::Router.url_for self.class, func, params
end

#valuesObject

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



52
53
54
# File 'lib/plezi/controller/cookies.rb', line 52

def values
   (@request ? (super + request.cookies.values) : super)
end

#write(data) ⇒ Object

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



138
139
140
# File 'lib/plezi/controller/controller.rb', line 138

def write data
   _pl__client.write data
end